(* ::Package:: *)

(* ::Section::Closed:: *)
(*PackageHeader*)


BeginPackage["CodeInspector`LinterUI`"]


Begin["`Private`"]


Needs["CodeParser`"]
Needs["CodeInspector`"]


(* ::Section::Closed:: *)
(*Appearance Elements*)


$UIRoundingRadius = 4;


colorData = With[
	{
		errorSev3 = RGBColor[0.9400000000000001, 0.64, 0],
		errorSev2 = RGBColor[1, 0.45, 0],
		errorSev1 = RGBColor[0.827451, 0.00392157, 0.00392157]
	},
	
	<|
		(* General. *)
		"UIBack" -> GrayLevel[.97],
		"UIEdge" -> GrayLevel[.85],
		"CloseButton" -> GrayLevel[.6],
		"CodeBack" -> RGBColor[0.99, 1, 1],
		"UIDark" -> (*RGBColor[0.53, 0.34, 0]*)GrayLevel[.4],
		"WarningText" -> RGBColor[0.89, 0.14, 0.05],
		"PopupEdge" -> GrayLevel[0.75],
		"PopupBack" -> GrayLevel[0.97],
		"Delimiter" -> GrayLevel[.85],

		(* Cell Bracket Button colors. *)
		"CellBracketButtonText" -> GrayLevel[.25],
		"CellBracketButtonBack" -> GrayLevel[.95],
		"CellBracketButtonHover" -> GrayLevel[.98],
		"CellBracketButtonEdge" -> GrayLevel[.9],
		
		(* Button colors. *)
		"ButtonBack" -> White,
		"ButtonBackHover" -> White,
		"ButtonBackMouseDown" -> Hue[0.55, 0.33, 1],
		"ButtonBackInactive" -> White,
		"ButtonEdge" -> GrayLevel[.8],
		"ButtonEdgeHover" -> Hue[0.55,0.82,0.87],
		"ButtonEdgeInactive" -> GrayLevel[.85],
		"ButtonText" -> GrayLevel[0.2],
		"ButtonTextHover" -> GrayLevel[0.2],
		"ButtonTextInactive" -> GrayLevel[0.7],
		"ApplyButtonText" -> RGBColor[1, 1, 1],
		"ApplyButtonBack" -> RGBColor[0.247059, 0.666667, 0.301961],
		"ApplyButtonBackHover" -> RGBColor[0.266667, 0.72549, 0.329412],
		"ApplyButtonEdge" -> RGBColor[0.266667, 0.733333, 0.329412],
		"HashButtonBack" -> White,
		"HashButtonBackHover" -> Hue[0.1, 0.26, 1],
		"HashButtonEdge" -> RGBColor[Rational[81, 85], 0.79, 0.37],
		
		(* Raft colors. *)
		"RaftBack" -> RGBColor[0.96, 0.97, 0.97],
		"RaftMenuBack" -> White,
		"RaftItemHighlight" -> RGBColor[0.96, 0.97, 0.97],
		"RaftBackHover" -> RGBColor[0.99, 1, 1],
		"RaftBackOpen" -> RGBColor[0.94, 0.95, 0.96],
		"RaftFrame" -> RGBColor["#C1D3E1"],
		"RaftLabel" -> GrayLevel[0.2],
		"RaftMenuItem" -> GrayLevel[0.2],
		"RaftDelimiter" -> GrayLevel[0.9],
		"CodeHighlight" -> RGBColor[1, 0.67, 0.73],
		
		(* Error types. *)
		"Formatting" -> errorSev3,
		"Remark" -> errorSev3,
		"ImplicitTimes" -> errorSev3,
		"Scoping" -> errorSev3,
		"Warning" -> errorSev2,
		"Error" -> errorSev1,
		"Fatal" -> errorSev1,
		3 -> errorSev3,
		2 -> errorSev2,
		1 -> errorSev1
	|>];


styleData = <|
	
	"SectionHeader" -> Function[
		Style[#1, ##2, FontColor -> colorData["UIDark"], FontFamily -> "Source Sans Pro", FontWeight -> Plain, FontSize -> 13]],
		
	"RaftLabel" -> Function[
		Style[Row[CodeInspector`Utils`boldify[#1]], ##2, FontColor -> colorData["RaftLabel"], FontFamily -> "Source Sans Pro", FontWeight -> Plain, FontSize -> 13]],
	
	"RaftMenuItem" -> Function[
		Style[#1, ##2, FontColor -> colorData["RaftMenuItem"], FontFamily -> "Source Sans Pro", FontWeight -> Plain, FontSize -> 13]],
	
	"Button" -> Function[
		Style[#1, ##2, FontColor -> colorData["ButtonText"], FontFamily -> "Source Sans Pro", FontWeight -> Plain, FontSize -> 14]],
	
	"ApplyButton" -> Function[
		Style[#1, ##2, FontColor -> colorData["ApplyButtonText"], FontFamily -> "Source Sans Pro", FontWeight -> Plain, FontSize -> 12]],
	
	"FooterText" -> Function[
		Style[#1, ##2, FontColor -> colorData["UIDark"], FontFamily -> "Source Sans Pro", FontWeight -> Plain, FontSize -> 12]],

	"CellBracketButton" -> Function[
		Style[#1, ##2, FontColor -> colorData["CellBracketButtonText"], FontFamily -> "Source Sans Pro", FontWeight -> "SemiBold", FontSize -> 10]],
	
	"FixedWidth" -> Function[
		Style[#1, ##2, FontColor -> colorData["ButtonText"], FontFamily -> "Source Code Pro", FontWeight -> "SemiBold", FontSize -> 12]]
|>;


inputStyle[boxes_, opts___] := StyleBox[boxes, "Input", opts, ShowStringCharacters -> True]


iconData = <|
	
	(* Exclamation mark. *)
	"Exclam" -> Function[color,
		Graphics[
			{EdgeForm[], FaceForm[color],
				FilledCurve[{{{1, 4, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}}, {{1, 4, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}}, 
					{{0, 2, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}}}, {{{9.015, 17.369999999999997}, {4.4558892299999995, 17.369999999999997}, {0.76, 13.64500928}, {0.76, 
					9.049999999999999}, {0.76, 4.454992000000001}, {4.4558892299999995, 0.7300000000000004}, {9.015, 0.7300000000000004}, {13.574109499999999, 0.7300000000000004}, {17.270000000000003, 4.454992000000001}, {17.270000000000003, 
					9.049999999999999}, {17.270000000000003, 13.64500928}, {13.574109499999999, 17.369999999999997}, {9.015, 17.369999999999997}}, {{10.196100000000001, 3.4052000000000007}, {9.87255988, 3.1110688000000017}, {9.450673499999999, 
					2.9510432000000026}, {9.015, 2.9572000000000003}, {8.57976338, 2.9535392000000016}, {8.15874695, 3.1132320000000036}, {7.833899999999999, 3.4052000000000007}, {7.4720592199999984, 3.726876800000001}, {7.279557699999998, 
					4.200092800000004}, {7.313199999999999, 4.6852}, {7.310677779999997, 5.125935999999998}, {7.49009957, 5.547884160000004}, {7.808499999999999, 5.850000000000001}, {8.11909247, 6.178172800000002}, {8.552426630000001, 6.359376000000001}, 
					{9.002299999999998, 6.3492000000000015}, {9.451585360000001, 6.356242560000002}, {9.883723179999999, 6.175539840000003}, {10.196100000000001, 5.850000000000001}, {10.52990934, 5.547096960000005}, {10.715518569999999, 5.112158079999995}, 
					{10.704099999999999, 4.659600000000001}, {10.72568619, 4.1865760000000005}, {10.53993091, 3.7279008000000005}, {10.196100000000001, 3.4052000000000007}}, {{11.046999999999997, 12.249999999999998}, {10.437399999999998, 9.0756}, 
					{10.39444606, 8.741139839999999}, {10.242574379999997, 8.430503040000001}, {10.0056, 8.192400000000001}, {9.700927, 7.958544000000002}, {9.320065430000001, 7.8488697599999995}, {8.9388, 7.885200000000001}, {8.56093055, 
					7.842825600000001}, {8.18181904, 7.953311360000001}, {7.8847, 8.192400000000001}, {7.65662578, 8.44187456}, {7.5064508199999995, 8.75349056}, {7.4529000000000005, 9.0884}, {7.0084, 12.249999999999998}, {6.939052919999998, 
					12.673503360000002}, {6.89663238, 13.10104256}, {6.881399999999999, 13.530000000000001}, {6.89486708, 13.91046976}, {7.136015949999997, 14.24466112}, {7.491, 14.374799999999999}, {8.000101090000001, 14.59409472}, {8.55050639, 
					14.698928000000002}, {9.1039, 14.681999999999999}, {9.67359533, 14.748591999999999}, {10.248492579999999, 14.612789119999999}, {10.729499999999998, 14.298}, {11.021667309999996, 14.000360319999999}, {11.170132849999996, 13.58886464}, 
					{11.135899999999998, 13.1716}, {11.140814899999997, 12.86200512}, {11.110992759999997, 12.55284288}, {11.046999999999997, 12.249999999999998}}}]},
			AspectRatio -> Automatic, ImageSize -> 14{18./18, 19./18}, PlotRange -> {{0.76, 17.27}, {0.73, 17.37}}, ImagePadding -> .5,
			BaselinePosition -> Scaled[.1]]],
	
	(* Tick Disk *)
	"TickDisk" -> Function[{back, fore},
	Graphics[{ 
  {FaceForm[back], FilledCurve[{{{1, 4, 3}, {1, 3, 3}, 
    {1, 3, 3}, {1, 3, 3}}}, {{{9., 4.5}, {9., 2.0147189999999995}, {6.9852810000000005, 
    0.}, {4.5, 0.}, {2.014719, 0.}, {0., 2.0147189999999995}, {0., 4.5}, {0., 
    6.9852810000000005}, {2.014719, 9.}, {4.5, 9.}, {6.9852810000000005, 9.}, {9., 
    6.9852810000000005}, {9., 4.5}}}]}, {FaceForm[fore],
   FilledCurve[{{{1, 4, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 
    3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, 
    {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}}}, {{{3.44, 
    3.3400000000000007}, {3.5874040000000003, 3.6843830000000004}, {3.757788, 
    4.018469}, {3.9499999999999997, 4.340000000000001}, {4.18, 4.76}, {4.42, 
    5.140000000000001}, {4.659999999999999, 5.5}, {4.8689279999999995, 5.815583}, 
    {5.099481, 6.116303}, {5.35, 6.4}, {5.489060000000001, 6.554081}, 
    {5.6359189999999995, 6.70094}, {5.79, 6.84}, {5.866028, 6.908950000000001}, 
    {5.954185999999999, 6.963201}, {6.05, 7.}, {6.384963999999999, 7.025477}, 
    {6.721852, 7.001895}, {7.05, 6.93}, {7.05, 6.93}, {7.13, 6.93}, {7.13, 6.88}, 
    {7.13, 6.83}, {7.13, 6.8100000000000005}, {7.05, 6.76}, {6.531218000000001, 
    6.255229}, {6.0776900000000005, 5.6874780000000005}, {5.7, 5.07}, {5.220852, 
    4.337005000000001}, {4.812353, 3.5601889999999994}, {4.48, 2.75}, 
    {4.359999999999999, 2.45}, {4.28, 2.16}, {4.22, 2.05}, {4.159999999999999, 
    1.9400000000000004}, {3.74, 1.87}, {3.48, 1.87}, {3.268459, 1.8547019999999996}, 
    {3.056923, 1.8997830000000002}, {2.8699999999999997, 2.}, {2.758893, 
    2.152515000000001}, {2.6649890000000003, 2.316847}, {2.59, 2.49}, {2.417694, 
    2.7582379999999986}, {2.2201280000000003, 3.0093819999999996}, {2., 3.24}, 
    {1.77714, 3.4591640000000003}, {1.573089, 3.6966660000000005}, {1.3900000000000001, 
    3.95}, {1.4024269999999999, 4.069742999999999}, {1.479075, 4.173218}, {1.59, 4.22}, 
    {1.770281, 4.328650999999999}, {1.98025, 4.377644000000001}, {2.19, 4.36}, 
    {2.348273, 4.341642}, {2.495986, 4.271303}, {2.61, 4.16}, {2.9114749999999994, 
    3.912972}, {3.1893360000000004, 3.638458}, {3.44, 3.3400000000000007}}}]}}, 
 AspectRatio -> Automatic, ImageSize -> 10{1., 1.}, PlotRange -> {{0., 9.}, {0., 9.}}, ImagePadding -> .5]
	],
	
	(* Wand *)
	"Wand" -> Function[color,
	Graphics[{Dynamic[FaceForm[color]], {FilledCurve[{{{0, 2, 0}, {1, 3, 3}, {0, 1, 0}, {0, 1, 
       0}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {0, 
       1, 0}, {1, 3, 3}}}, {{{2., 12.39}, {2.29, 
       11.5}, {2.4181459999999997, 11.11229}, {2.72229, 
       10.808146}, {3.11, 10.68}, {4., 10.39}, {3.11, 
       10.1}, {2.72229, 9.971854}, {2.4181459999999997, 
       9.66771}, {2.29, 9.280000000000001}, {2., 8.39}, {1.71, 
       9.280000000000001}, {1.5818539999999999, 9.66771}, {1.27771, 
       9.971854}, {0.89, 10.1}, {0., 10.39}, {0.89, 10.68}, {1.27771, 
       10.808146}, {1.5818539999999999, 11.11229}, {1.71, 
       11.5}}}]}, {FilledCurve[{{{0, 2, 0}, {1, 3, 3}, {0, 1, 0}, {0, 
       1, 0}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {0, 1, 
       0}, {0, 1, 0}, {1, 3, 3}}}, {{{9.78, 15.}, {10.07, 
       14.11}, {10.198149999999998, 13.722290000000001}, {10.50229, 
       13.418146}, {10.89, 13.29}, {11.78, 13.}, {10.89, 
       12.71}, {10.50229, 12.581854}, {10.198149999999998, 
       12.277709999999999}, {10.07, 11.89}, {9.78, 11.}, {9.49, 
       11.89}, {9.361854000000001, 12.277709999999999}, {9.05771, 
       12.581854}, {8.67, 12.71}, {7.78, 13.}, {8.67, 
       13.29}, {9.05771, 13.418146}, {9.361854000000001, 
       13.722290000000001}, {9.49, 14.11}}}]}, {FilledCurve[{{{0, 2, 
       0}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {0, 
       1, 0}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}}}, {{{11.78, 
       9.79}, {12.07, 8.91}, {12.19686, 8.519324}, {12.50089, 
       8.211584}, {12.89, 8.08}, {13.78, 7.79}, {12.89, 
       7.5}, {12.50055, 7.374698}, {12.1953, 7.069455}, {12.07, 
       6.68}, {11.78, 5.790000000000001}, {11.49, 
       6.68}, {11.364700000000001, 7.069455}, {11.059450000000002, 
       7.374698}, {10.67, 7.5}, {9.78, 7.79}, {10.67, 
       8.08}, {11.05911, 8.211584}, {11.363140000000001, 
       8.519324}, {11.49, 8.91}}}]}, {FilledCurve[{{{0, 2, 0}, {0, 1, 
       0}, {0, 1, 0}}, {{0, 2, 0}, {0, 1, 0}, {0, 1, 0}}}, {{{6.91, 
       11.}, {-0.8400000000000001, -1.3300000000000018}, {1.74, -3.}, \
{9.49, 9.36}}, {{5.67, 7.5}, {7.1499999999999995, 
       9.83}, {8.370000000000001, 9.059999999999999}, {6.89, 
       6.73}}}]}}, AspectRatio -> Automatic, 
 ImageSize -> 16 {14./15, 15./15}, 
 PlotRange -> {{0., 13.7}, {0., 15.}}, ImagePadding -> .5, BaselinePosition -> Scaled[.2]],
 HoldAll],
	
	(* Cell Bracket Icon. *)
	"GoToPod" -> Function[color,
		Graphics[{FaceForm[color],
  FilledCurve[{{{0, 2, 0}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, 
      {0, 1, 0}, {1, 3, 3}}}, {{{4., 12.}, {21., 12.}, {23.2112, 12.}, {25., 10.2112}, 
      {25., 8.}, {25., 4.}, {25., 1.788800000000002}, {23.2112, 0.}, {21., 0.}, {4., 
      0.}, {1.7888, 0.}, {0., 1.788800000000002}, {0., 4.}, {0., 8.}, {0., 10.2112}, 
      {1.7888, 12.}, {4., 12.}}}],
	  FaceForm[White],
	  FilledCurve[{{{1, 4, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, 
      {1, 3, 3}}}, {{{6.54, 2.6499999999999986}, {6.54, 3.0072659999999978}, 
      {6.730599000000001, 3.3373929999999987}, {7.04, 3.516025000000001}, {7.349401, 
      3.6946579999999987}, {7.730599000000001, 3.6946579999999987}, {8.04, 
      3.516025000000001}, {8.349401, 3.3373929999999987}, {8.54, 3.0072659999999978}, 
      {8.54, 2.6499999999999986}, {8.557882000000001, 2.379697000000002}, {8.458281, 
      2.114819999999998}, {8.266731, 1.9232699999999987}, {8.075180000000001, 
      1.731720000000001}, {7.810303, 1.6321199999999987}, {7.54, 1.6499999999999986}, 
      {6.987715, 1.6499999999999986}, {6.54, 2.097714999999999}, {6.54, 
      2.6499999999999986}}}], 
  FilledCurve[{{{0, 2, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}}}, 
     {{{6.91, 5.09}, {6.659999999999999, 10.07}, {8.4, 10.07}, {8.16, 5.09}, {6.91, 
      5.09}}}],
  FilledCurve[{{{0, 2, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, 
      {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}}}, 
     {{{16.21, 3.9499999999999993}, {13.129999999999999, 7.}, {13.03534, 7.093883}, 
      {12.9821, 7.221680999999999}, {12.9821, 7.355}, {12.9821, 7.488319}, {13.03534, 
      7.616117}, {13.129999999999999, 7.71}, {13.22388, 7.8046560000000005}, 
      {13.351680000000002, 7.857899000000001}, {13.485000000000001, 7.857899000000001}, 
      {13.618319999999997, 7.857899000000001}, {13.746119999999998, 7.8046560000000005}, 
      {13.84, 7.71}, {16.21, 5.37}, {18.59, 7.7}, {18.683880000000002, 7.794656}, 
      {18.811680000000003, 7.847899000000001}, {18.944999999999997, 7.847899000000001}, 
      {19.078319999999998, 7.847899000000001}, {19.20612, 7.794656}, {19.3, 7.7}, 
      {19.394660000000002, 7.606117}, {19.4479, 7.478319}, {19.4479, 7.345000000000001}, 
      {19.4479, 7.211681}, {19.394660000000002, 7.083883}, {19.3, 6.99}}}]
   }, ImageSize -> 31{25./25, 12./25}, 
 PlotRange -> {{0., 25.}, {0., 12.}}, AspectRatio -> Automatic, ImagePadding -> 1]
	],

	(* Take-action chevron. *)
	"TakeAction" -> Function[color,
		(*With[
			{chevron = Function[xPos, Line[{{-.5+xPos, 1}, {.5+xPos, 0}, {-.5+xPos, -1}}]]},
			Graphics[
				{
					color, AbsoluteThickness[1.8], CapForm["Round"], JoinForm["Miter"],
					chevron[0], chevron[1.5]},
				AspectRatio \[Rule] Full, ImageSize \[Rule] .7{13, 11}, BaselinePosition \[Rule] Bottom]]*)
		With[
			{chevron = Function[xPos, Line[{{-.5+xPos, 1}, {.5+xPos, 0}, {-.5+xPos, -1}}]]},
			Graphics[
				{
					color, AbsoluteThickness[1.8], CapForm["Round"], JoinForm["Miter"],
					chevron[0]},
				AspectRatio -> Full, ImageSize -> .7{8, 11}, BaselinePosition -> Bottom, ImageMargins -> {{0, 4}, {0, 0}}]]],
	
	(* Ignore error in cell. *)
	"IgnoreInCell" -> Function[color,
		Graphics[{EdgeForm[], FaceForm[color], { 
			FilledCurve[{{{0, 2, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}}, {{0, 2, 0}, {0, 1, 0}}}, 
				{{{11.69, 13.37}, {7.57, 13.37}, {7.57, 12.37}, {7.76, 12.37}, {11.19, 8.93}, {11.19, 1.2499999999999982}, {7.57, 1.2499999999999982}, {7.57, 0.24999999999999822}, {12.19, 0.24999999999999822}, {12.19, 13.37}},
				{{9.17, 12.37}, {11.17, 12.37}, {11.17, 10.37}}}]}, { 
				FilledCurve[{{{0, 2, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, 
				{0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}}}, {{{5.319999999999999, 6.81}, {7.659999999999999, 9.139999999999999}, {7.851783, 9.337814}, {7.851783, 9.652185999999999}, {7.659999999999999, 9.85}, 
				{7.566116999999999, 9.944655999999998}, {7.438319000000001, 9.997898999999999}, {7.305, 9.997898999999999}, {7.171681, 9.997898999999999}, {7.043883, 9.944655999999998}, {6.95, 9.85}, 
				{4.619999999999999, 7.52}, {2.2800000000000002, 9.85}, {2.189267, 9.946677}, {2.062586, 10.001518999999998}, {1.93, 10.001518999999998}, {1.7974139999999998, 10.001518999999998}, {1.670733, 9.946677}, 
				{1.58, 9.85}, {1.4853439999999998, 9.756117}, {1.4321009999999998, 9.628319}, {1.4321009999999998, 9.495000000000001}, {1.4321009999999998, 9.361680999999999}, {1.4853439999999998, 9.233882999999999}, 
				{1.58, 9.139999999999999}, {3.9099999999999997, 6.81}, {1.58, 4.469999999999999}, {1.4833229999999997, 4.3792670000000005}, {1.428481, 4.252585999999997}, {1.428481, 4.119999999999999}, {1.428481, 3.9874139999999976},
				{1.4833229999999997, 3.8607329999999997}, {1.58, 3.769999999999998}, {1.6702800000000002, 3.6728620000000003}, {1.7973959999999998, 3.61838}, {1.93, 3.619999999999999}, {2.062118, 3.6207689999999992},
				{2.1883270000000006, 3.6748589999999997}, {2.2800000000000002, 3.769999999999998}, {4.619999999999999, 6.1}, {7., 3.769999999999998}, {7.079746000000001, 3.6869499999999995}, 
				{7.185712, 3.6339669999999984}, {7.3, 3.619999999999999}, {7.435134, 3.6203309999999984}, {7.5646119999999994, 3.6742799999999995}, {7.659999999999999, 3.769999999999998}, {7.8505709999999995, 3.964421999999999},
				{7.8505709999999995, 4.275577999999999}, {7.659999999999999, 4.469999999999999}}}]}},
				AspectRatio -> Automatic, ImageSize -> {14., 14.}, PlotRange -> {{-.5, 13.62}, {-.5, 13.62}}, ImageMargins -> {{0, 0}, {0, 2}}]],
	
	(* Ignore error in notebook. *)
	"IgnoreInNotebook" -> Function[color,
		Graphics[{FaceForm[color], {FilledCurve[{{{0, 2, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 
       3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {1, 
       3, 3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {1, 3, 
       3}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {1, 
       3, 3}, {1, 3, 3}}}, {{{4.08, 5.81}, {6.41, 
       8.139999999999999}, {6.504656, 8.233882999999999}, {6.557899, 
       8.361680999999999}, {6.557899, 8.495000000000001}, {6.557899, 
       8.628318999999998}, {6.504656, 8.756117}, {6.41, 
       8.849999999999998}, {6.319267000000001, 
       8.946676999999998}, {6.192585999999999, 9.001519}, {6.06, 
       9.001519}, {5.927413999999999, 9.001519}, {5.800733, 
       8.946676999999998}, {5.71, 
       8.849999999999998}, {3.3699999999999997, 6.52}, {1., 
       8.849999999999998}, {0.906117, 8.944655999999998}, {0.778319, 
       8.997899}, {0.645, 8.997899}, {0.511681, 
       8.997899}, {0.38388300000000003, 
       8.944655999999998}, {0.29000000000000004, 
       8.849999999999998}, {0.19534400000000002, 
       8.756117}, {0.142101, 8.628318999999998}, {0.142101, 
       8.495000000000001}, {0.142101, 
       8.361680999999999}, {0.19534400000000002, 
       8.233882999999999}, {0.29000000000000004, 
       8.139999999999999}, {2.66, 5.81}, {0.32999999999999996, 
       3.469999999999999}, {0.233323, 3.37927}, {0.17848100000000003, 
       3.252589999999998}, {0.17848100000000003, 
       3.119999999999999}, {0.17848100000000003, 
       2.987409999999999}, {0.233323, 2.86073}, {0.32999999999999996, 
       2.769999999999998}, {0.42231500000000005, 
       2.675790000000001}, {0.548115, 
       2.621880000000001}, {0.6799999999999999, 
       2.619999999999999}, {0.801135, 
       2.630259999999998}, {0.9146200000000001, 
       2.6834599999999984}, {1., 
       2.769999999999998}, {3.3699999999999997, 5.1}, {5.71, 
       2.769999999999998}, {5.80028, 2.67286}, {5.927396, 
       2.61838}, {6.06, 2.619999999999999}, {6.260038, 
       2.6228099999999994}, {6.439144000000001, 
       2.744589999999997}, {6.515315999999999, 
       2.9295799999999996}, {6.591488, 3.1145599999999973}, {6.55006, 
       3.3271499999999996}, {6.41, 
       3.469999999999999}}}]}, {FilledCurve[{{{0, 2, 0}, {1, 3, 
       3}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {1, 
       3, 3}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, {1, 3, 
       3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {1, 
       3, 3}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}}}, {{{11.41, 
       13.37}, {5.56, 13.37}, {4.455431, 13.37}, {3.56, 
       12.474568999999999}, {3.56, 11.37}, {3.56, 8.87}, {4.56, 
       9.87}, {4.56, 11.35}, {4.56, 11.902285}, {5.007714999999999, 
       12.35}, {5.56, 12.35}, {11.41, 12.35}, {11.962280000000002, 
       12.35}, {12.41, 11.902285}, {12.41, 11.35}, {12.41, 
       2.269999999999998}, {12.41, 
       1.7177199999999981}, {11.962280000000002, 
       1.2699999999999978}, {11.41, 1.2699999999999978}, {6.76, 
       1.2699999999999978}, {6.535926, 
       1.1865599999999983}, {6.2990900000000005, 
       1.1425799999999988}, {6.06, 1.1399999999999988}, {5.754477, 
       1.1418599999999994}, {5.453442000000001, 
       1.2136999999999993}, {5.18, 
       1.3499999999999996}, {4.983297000000001, 
       1.4418100000000003}, {4.803980999999999, 
       1.5669899999999988}, {4.6499999999999995, 
       1.7199999999999989}, {3.65, 2.7799999999999994}, {3.65, 
       2.2799999999999994}, {3.65, 1.1754300000000004}, {4.545431, 
       0.27999999999999936}, {5.6499999999999995, 
       0.27999999999999936}, {11.5, 
       0.27999999999999936}, {12.604569999999999, 
       0.27999999999999936}, {13.5, 1.1754300000000004}, {13.5, 
       2.2799999999999994}, {13.5, 11.35}, {13.50606, 
       11.899863999999999}, {13.28545, 
       12.427964999999999}, {12.89005, 
       12.810122999999999}, {12.49465, 
       13.192281}, {11.959340000000001, 13.394779}, {11.41, 
       13.37}}}]}, {FilledCurve[{{{0, 2, 0}, {1, 3, 3}, {0, 1, 
       0}, {0, 1, 0}}}, {{{5.63, 11.049999999999999}, {5.63, 
       10.44}, {6.05695, 10.538877999999999}, {6.50484, 
       10.489504}, {6.9, 10.299999999999999}, {9.53, 
       10.299999999999999}, {9.53, 
       11.049999999999999}}}]}, {FilledCurve[{{{1, 4, 3}, {0, 1, 
       0}, {0, 1, 0}}}, {{{7.92, 3.7299999999999986}, {8.03962, 
       3.499369999999999}, {8.078356, 3.2352599999999985}, {8.03, 
       2.9800000000000004}, {9.41, 2.9800000000000004}, {9.41, 
       3.7299999999999986}}}]}, {FilledCurve[{{{0, 2, 0}, {0, 1, 
       0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}}}, {{{6.73, 
       6.209999999999999}, {6.2, 5.819999999999999}, {6.67, 
       5.459999999999999}, {11.219999999999999, 
       5.459999999999999}, {11.219999999999999, 
       6.209999999999999}, {6.73, 
       6.209999999999999}}}]}, {FilledCurve[{{{1, 4, 3}, {0, 1, 
       0}, {0, 1, 0}}}, {{{8., 8.7}, {8.051429, 
       8.44585}, {8.016221999999999, 8.181795999999999}, {7.9, 
       7.949999999999999}, {10.25, 7.949999999999999}, {10.25, 
       8.7}}}]}}, AspectRatio -> Automatic, 
 ImageSize -> 15{14./14, 14./14}, 
 PlotRange -> {{0., 13.62}, {0., 13.62}}, ImagePadding -> .75]],
	
	(* Ignore error always. *)
	"IgnoreAlways" -> Function[color,
		Graphics[{EdgeForm[], FaceForm[color], FilledCurve[{{{0, 2, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {0, 
			1, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}}}, {{{7.52, 6.81}, 
			{10.739999999999998, 10.03}, {10.83466, 10.123883}, {10.8879, 10.251681}, {10.8879, 10.385}, {10.8879, 10.518318999999998}, {10.83466, 10.646117}, {10.739999999999998, 10.739999999999998}, 
			{10.649269999999998, 10.836676999999998}, {10.522590000000001, 10.891518999999999}, {10.39, 10.891518999999999}, {10.25741, 10.891518999999999}, {10.13073, 10.836676999999998}, {10.04, 
			10.739999999999998}, {6.81, 7.52}, {3.58, 10.739999999999998}, {3.489267, 10.836676999999998}, {3.362586, 10.891518999999999}, {3.23, 10.891518999999999}, {3.097414, 10.891518999999999}, {2.970733, 
			10.836676999999998}, {2.88, 10.739999999999998}, {2.7853440000000003, 10.646117}, {2.732101, 10.518318999999998}, {2.732101, 10.385}, {2.732101, 10.251681}, {2.7853440000000003, 10.123883}, {2.88, 
			10.03}, {6.1, 6.81}, {2.88, 3.619999999999999}, {2.73994, 3.47715}, {2.698512, 3.264559999999996}, {2.774684, 3.07958}, {2.8508559999999994, 2.894589999999999}, {3.029962, 2.7728099999999998}, {3.23, 
			2.769999999999998}, {3.3626039999999997, 2.7683799999999987}, {3.48972, 2.8228600000000004}, {3.58, 2.92}, {6.81, 6.1}, {10., 2.880000000000001}, {10.091669999999999, 2.7848599999999983}, {10.21788, 
			2.730769999999998}, {10.350000000000001, 2.7299999999999986}, {10.482600000000001, 2.7283799999999996}, {10.60972, 2.7828599999999994}, {10.7, 2.880000000000001}, {10.79668, 2.9707300000000014}, 
			{10.85152, 3.097409999999998}, {10.85152, 3.2299999999999986}, {10.85152, 3.362589999999999}, {10.79668, 3.4892699999999994}, {10.7, 3.58}}}]},
			AspectRatio -> Automatic, ImageSize -> {14., 14.}, PlotRange -> {{-.5, 13.62}, {-.5, 13.62}}, ImageMargins -> {{0, 0}, {0, 2}}]],
	
	(* Info (used for documentation links). *)
	"Info" -> Function[color,
		Graphics[{FaceForm[color], 
			FilledCurve[{{{1, 4, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}}, {{1, 4, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 
			3}}, {{1, 4, 3}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}}}, {{{6.81, 13.}, {3.3913569999999997, 13.}, {0.62, 10.228643}, {0.62, 6.81}, 
			{0.62, 3.3913600000000006}, {3.3913569999999997, 0.6199999999999992}, {6.81, 0.6199999999999992}, {10.228639999999999, 0.6199999999999992}, {13., 
			3.3913600000000006}, {13., 6.81}, {13., 10.228643}, {10.228639999999999, 13.}, {6.81, 13.}}, {{6.81, 3.6899999999999995}, {6.92, 3.5299999999999994}, {7.94, 
			4.4399999999999995}, {8., 4.0699999999999985}, {8.06, 3.6999999999999993}, {7.44, 3.1899999999999995}, {7.13, 2.969999999999999}, {6.621312, 2.67488}, {6.037101, 
			2.535780000000001}, {5.45, 2.5699999999999985}, {4.83, 2.629999999999999}, {4.67, 3.259999999999998}, {4.67, 3.6599999999999984}, {4.851589, 4.494925}, {5.09903, 
			5.314153999999997}, {5.41, 6.109999999999999}, {5.54, 6.449999999999999}, {6.159999999999999, 8.}, {6.159999999999999, 8.}, {6.359999999999999, 8.51}, {7.46, 
			8.309999999999999}, {7.8, 8.25}, {8.139999999999999, 8.189999999999998}, {8.05, 7.659999999999999}, {7.859999999999999, 7.249999999999999}, {7.67, 
			6.839999999999999}, {7.24, 5.919999999999999}, {7.06, 5.34}, {6.878061000000001, 4.809523999999998}, {6.79337, 4.250561999999997}, {6.81, 3.6899999999999995}}, 
			{{8.81, 9.79}, {8.810053, 9.512194000000001}, {8.587752, 9.285447}, {8.31, 9.28}, {7.51, 9.28}, {7.2283349999999995, 9.28}, {7., 9.508334999999999}, {7., 9.79}, 
			{7., 10.62}, {7.005447, 10.897752}, {7.232194, 11.120052999999999}, {7.51, 11.12}, {8.350000000000001, 11.12}, {8.623907999999998, 11.11468}, {8.84468, 10.893908}, 
			{8.850000000000001, 10.62}}}]},
			AspectRatio -> Automatic, ImageSize -> {14., 14.}, PlotRange -> {{0., 13.62}, {0., 13.62}}, ImagePadding -> .5]],
	
	(* Link open arrow. *)
	"OpenLinkArrow" -> Function[color,
		Graphics[{FaceForm[color], 
			FilledCurve[{{{1, 4, 3}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {0, 1, 
			0}, {1, 3, 3}}}, {{{10.360000000000001, 10.09}, {10.311209999999999, 10.213465}, {10.21347, 10.311209999999999}, {10.09, 10.36}, {10.02754, 10.374876999999998}, 
			{9.962460000000002, 10.374876999999998}, {9.9, 10.36}, {6.08, 10.36}, {5.803858, 10.36}, {5.58, 10.136142}, {5.58, 9.86}, {5.58, 9.583858}, {5.803858, 9.36}, {6.08, 
			9.36}, {8.7, 9.36}, {3.3699999999999997, 4.0699999999999985}, {3.198604, 3.8728380000000016}, {3.2078830000000003, 3.5769599999999997}, {3.391297, 
			3.390930000000001}, {3.574711, 3.204889999999999}, {3.870427, 3.191419999999999}, {4.07, 3.3599999999999994}, {9.4, 8.689999999999998}, {9.4, 6.079999999999999}, 
			{9.4, 5.803857999999999}, {9.623857999999998, 5.58}, {9.9, 5.58}, {10.17614, 5.58}, {10.4, 5.803857999999999}, {10.4, 6.079999999999999}, {10.4, 9.899999999999999}, 
			{10.40207, 9.965622}, {10.388349999999999, 10.030783}, {10.360000000000001, 10.09}}}]},
			AspectRatio -> Automatic, ImageSize -> {14., 14.}, PlotRange -> {{0., 13.62}, {0., 13.62}}(*, ImageMargins \[Rule] {{0, 0}, {0, 2}}*)]],
	
	(* Pop out icon. *)
	"PopOut" -> (*Function[color,
		Graphics[{
			color, Disk[{0, 0}, 1],
			CapForm["Round"], White,
			AbsoluteThickness[1.5], Line[{{.5{-1, 0}, .5{1, 0}}, {.5{0, 1}, .5{0, -1}}}]},
			PlotRange -> 1, ImagePadding -> 1, ImageSize -> 15{1, 1}]]*)
	Function[color,
		Graphics[{FaceForm[color],
		FilledCurve[{{{1, 4, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}}, {{1, 4, 3}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, 
      {0, 1, 0}, {0, 1, 0}, {1, 3, 3}}, {{1, 4, 3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}}}, 
     {{{7., 14.}, {3.134007, 14.}, {0., 10.865993}, {0., 7.}, {0., 3.134009999999998}, {3.134007, 0.}, {7., 0.}, {10.865990000000002, 0.}, {14., 3.134009999999998}, {14., 7.}, {14., 8.856515000000002}, 
      {13.262500000000001, 10.636993}, {11.94975, 11.949747}, {10.636989999999999, 13.262502}, {8.856515000000002, 14.}, {7., 14.}}, {{10.41, 3.6499999999999986}, {10.41, 3.3738600000000005}, {10.18614, 
      3.1499999999999986}, {9.91, 3.1499999999999986}, {3.8499999999999996, 3.1499999999999986}, {3.5738579999999995, 3.1499999999999986}, {3.3499999999999996, 3.3738600000000005}, {3.3499999999999996, 
      3.6499999999999986}, {3.3499999999999996, 9.71}, {3.35532, 9.983908}, {3.5760919999999996, 10.20468}, {3.8499999999999996, 10.21}, {5.51, 10.21}, {5.544165, 9.823222}, {5.726979999999999, 9.464761}, {6.02, 9.21}, 
      {4.35, 9.21}, {4.35, 4.149999999999999}, {9.41, 4.149999999999999}, {9.41, 5.8100000000000005}, {9.667296, 5.520240999999999}, {10.024369999999998, 5.338131000000001}, {10.41, 5.300000000000001}}, {{11., 7.}, {11., 
      6.723857999999999}, {10.77614, 6.5}, {10.5, 6.5}, {10.223859999999998, 6.5}, {10., 6.723857999999999}, {10., 7.}, {10., 9.33}, {6.71, 6.}, {6.614611999999999, 5.90428}, {6.4851339999999995, 5.850331000000001}, 
      {6.35, 5.85}, {6.218114999999999, 5.851879000000004}, {6.092314999999999, 5.905793000000001}, {6., 6.}, {5.905344, 6.093883}, {5.852100999999999, 6.221680999999999}, {5.852100999999999, 6.355}, {5.852100999999999, 
      6.488319}, {5.905344, 6.616117}, {6., 6.71}, {9.33, 10.}, {7., 10.}, {6.723858, 10.}, {6.5, 10.223858}, {6.5, 10.5}, {6.5, 10.776142}, {6.723858, 11.}, {7., 11.}, {10.54, 11.}, {10.60299, 11.009306}, {10.66701, 
      11.009306}, {10.729999999999999, 11.}, {10.85157, 10.948358}, {10.948359999999997, 10.851567}, {11., 10.73}, {11.00931, 10.667009}, {11.00931, 10.602991}, {11., 10.54}}}]},
		ImageSize -> 15{1, 1}, PlotRange -> {{0., 14.}, {0., 14.}}, ImagePadding -> {{1, 1}, {1, 1}}, AspectRatio -> Automatic]
		]
		(*Function[color,
		Graphics[{ 
  EdgeForm[], FaceForm[color], 
  FilledCurve[{{{1, 4, 3}, {1, 3, 3}, {1, 3, 3}, {1, 3, 3}}, {{0, 2, 0}, {1, 3, 3}, {0, 
    1, 0}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}}, {{1, 4, 
    3}, {0, 1, 0}, {1, 3, 3}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}, {0, 1, 0}, {1, 3, 3}}}, 
   {{{7., 13.5}, {3.4101489999999997, 13.5}, {0.5, 10.589851}, {0.5, 7.}, {0.5, 
    3.41015}, {3.4101489999999997, 0.5}, {7., 0.5}, {10.58985, 0.5}, {13.5, 3.41015}, 
    {13.5, 7.}, {13.5, 10.589851}, {10.58985, 13.5}, {7., 13.5}}, {{4.609999999999999, 
    7.22}, {4.609999999999999, 5.460000000000001}, {4.082803999999999, 
    5.687240999999998}, {3.7409510000000004, 6.205916000000001}, {3.74, 6.78}, {3.74, 
    9.25}, {3.74, 10.04529}, {4.384709999999999, 10.690000000000001}, {5.18, 
    10.690000000000001}, {6.94, 10.690000000000001}, {7.570081, 10.697118}, 
    {8.130562999999999, 10.290972}, {8.32, 9.690000000000001}, {7.06, 
    9.690000000000001}, {6.406739000000001, 9.690021999999999}, {5.780522, 9.429154}, 
    {5.320479, 8.965354999999999}, {4.860434999999999, 8.501556}, {4.604667, 7.873239}, 
    {4.609999999999999, 7.22}}, {{10.26, 4.75}, {10.26, 3.9547100000000004}, {9.61529, 
    3.3100000000000005}, {8.82, 3.3100000000000005}, {7.06, 3.3100000000000005}, 
    {6.676359000000001, 3.30734}, {6.307514999999999, 3.4578699999999998}, 
    {6.035296000000001, 3.7282100000000007}, {5.763077, 3.9985599999999994}, {5.609991, 
    4.366349000000001}, {5.609999999999999, 4.75}, {5.609999999999999, 7.22}, 
    {5.615495999999999, 8.016905}, {6.263076, 8.660019}, {7.06, 8.66}, {8.82, 8.66}, 
    {9.613020999999998, 8.654549}, {10.25455, 8.013020999999998}, {10.26, 7.22}}}]}, 
 AspectRatio -> Automatic, ImageSize -> 15{1, 1}, PlotRange -> {{0., 14.}, {0., 14.}}]]*),
	
	(* Opener chevrons. *)
	"DownChevron" -> Function[color, Graphics[
		{color, AbsoluteThickness[2], CapForm["Round"],
			Line[{{-1, 0}, {0, -1}, {1, 0}}]},
		ImagePadding -> {3{1, 1}, 3{1, 1}}, ImageSize -> {18, 11}, AspectRatio -> Full, BaselinePosition -> Bottom]],
	"UpChevron" -> Function[color, Graphics[
		{color, AbsoluteThickness[2], CapForm["Round"],
			Line[{{-1, 0}, {0, 1}, {1, 0}}]},
		ImagePadding -> {3{1, 1}, 3{1, 1}}, ImageSize -> {18, 11}, AspectRatio -> Full, BaselinePosition -> Bottom]]
|>;


closeIcon[offset_, pos_] := With[{crossSize = 2, diskRad = 6},
	{
		colorData["CloseButton"],
		Disk[Offset[offset, pos], Offset[diskRad]],
		colorData["UIBack"], AbsoluteThickness[1.5], CapForm["Round"],
		Line[{{Offset[offset + crossSize {-1, 1}, pos], Offset[offset + crossSize {1, -1}, pos]},
			{Offset[offset + crossSize {-1, -1}, pos], Offset[offset + crossSize {1, 1}, pos]}}]}]


(* ::Text:: *)
(*bezierCirclePts returns the control points of an approximate Bezier curve for each quadrant of the unit circle.*)


bezierCirclePts = With[{c = .551915024494},
	<|
		"Q1" -> {{1, 0}, {1, 0}, {1, c}, {c, 1}, {0, 1}, {0, 1}},
		"Q2" -> {{0, 1}, {0, 1}, {-c, 1}, {-1, c}, {-1, 0}, {-1, 0}},
		"Q3" -> {{-1, 0}, {-1, 0}, {-1, -c}, {-c, -1}, {0, -1}, {0, -1}},
		"Q4" -> {{0, -1}, {0, -1}, {c, -1}, {1, -c}, {1, 0}, {1, 0}}
	|>
];


SetAttributes[button, HoldRest]


button[
	disp_, action_,
	OptionsPattern[{
		ImageSize -> {Automatic, 19},
		FrameMargins -> {9{1, 1}, 0{1, 1}},
		BaselinePosition -> Baseline,
		Alignment -> {Center, Center},
		Method -> "Preemptive",
		"ActiveQ" -> True,
		(* Hover colors for the text, background and frame. *)
		"TextColor" -> colorData["ButtonText"],
		"TextHoverColor" -> colorData["ButtonTextHover"],
		"TextInactiveColor" -> colorData["ButtonTextInactive"],
		"BackColor" -> colorData["ButtonBack"],
		"BackHoverColor" -> colorData["ButtonBackHover"],
		"BackMouseDownColor" -> colorData["ButtonBackMouseDown"],
		"BackInactiveColor" -> colorData["ButtonBackInactive"],
		"EdgeColor" -> colorData["ButtonEdge"],
		"EdgeHoverColor" -> colorData["ButtonEdgeHover"],
		"EdgeInactiveColor" -> colorData["ButtonEdgeInactive"]
	}]
] :=
	DynamicModule[{hoverQ = False, mouseDownQ = False, fontColor},
		Button[
			DynamicWrapper[
				Highlighted[
					DynamicWrapper[
						styleData["Button"][
							(* The button display might need to know the state of the button (for example, changing the color of an icon on hover).
								Therefore, check if disp is a Function, and supply it with fontColor, hoverQ, and mouseDownQ if so. *)
							Pane[
								If[Head[disp] === Function, disp[fontColor, hoverQ, mouseDownQ], disp],
								ContentPadding -> True, FrameMargins -> None, BaselinePosition -> Baseline],
							FontColor -> Dynamic @ fontColor],
						
						fontColor = Which[
							TrueQ[!OptionValue["ActiveQ"]], OptionValue["TextInactiveColor"],
							hoverQ, OptionValue["TextHoverColor"],
							True, OptionValue["TextColor"]]],
					
					ContentPadding -> False,
					FrameMargins -> OptionValue[FrameMargins], Alignment -> OptionValue[Alignment],
					Frame -> True, ImageSize -> OptionValue[ImageSize], RoundingRadius -> 3,
					
					FrameStyle -> Dynamic @ Directive[AbsoluteThickness[.5], Which[
						TrueQ[!OptionValue["ActiveQ"]], OptionValue["EdgeInactiveColor"],
						hoverQ, OptionValue["EdgeHoverColor"],
						True, OptionValue["EdgeColor"]]],
					
					Background -> Dynamic @ Which[
						TrueQ[!OptionValue["ActiveQ"]], OptionValue["BackInactiveColor"],
						hoverQ && mouseDownQ, OptionValue["BackMouseDownColor"],
						hoverQ, OptionValue["BackHoverColor"],
						True, OptionValue["BackColor"]]],
				
				hoverQ = CurrentValue["MouseOver"];
				mouseDownQ = CurrentValue["MouseButtonTest"]],

			If[OptionValue["ActiveQ"] =!= False, action],
			
			Appearance -> None, ContentPadding -> False,
			BaselinePosition -> OptionValue[BaselinePosition],
			Method -> OptionValue[Method]]]


popupPane[
	contents_,
	{width_, height_},
	caretPos_ /; Between[caretPos, {-1, 1}],
	OptionsPattern[{
		Alignment -> {Left, Top},
		Background -> colorData["PopupBack"],
		FrameStyle -> Directive[{AbsoluteThickness[1], colorData["PopupEdge"]}]
	}]
] :=
	(* safetyPadding is added to each edge to ensure that no lines are clipped when rendered by the FE. *)
	With[{roundingRad = 4, caretH = 6, caretW = 12, safetyPadding = 3},
		Overlay[
			{
				Pane[
					(* The background of the popup is a pane with a caret at position caretPos along the top edge. *)
					Graphics[
						{
							EdgeForm[OptionValue[FrameStyle]], FaceForm[OptionValue[Background]],
							(* We want to be able to specify caretPos to be between -1 and 1 along the top edge. Therefore, the centre
								of each rounded corner needs to lie a distance of caretW/2 outside the region {{-1, 1}, {-1, 1}}.
								This means that at caretPos -1 or 1, the rounding of the corner will start at the very edge of the caret.
								Hence, each corner coord needs to be:
									scaled by the rounding radius: roundingRad(#),
									shifted a distance {1, 1} away from the {{-1, 1}, {-1, 1}} region: + (caretW/2){1, 1},
									shifted down by the caret height for the top two corners: + {0, -caretH}. *)
							FilledCurve[BezierCurve[Join[
								(* Top right corner. *)
								Offset[roundingRad(#) + (caretW/2){1, 1} + {0, -caretH}, {1, 1}]& /@ bezierCirclePts["Q1"][[2;;]],
								(* Caret. *)
								(* Seeing as the frame lies a distance (roundingRad + caretW/2) from the region {{-1, 1}, {-1, 1}}, so does the caret itself. *)
								Splice[Table[Offset[#, {caretPos, 1}], 3(* Verticies of an order 3 Bezier curve are three coincident points. *)]]& /@{
									{caretW/2, -caretH + caretW/2 + roundingRad},
									{0, caretW/2 + roundingRad},
									{-caretW/2, -caretH + caretW/2 + roundingRad}},
								(* Top left corner. *)
								Offset[roundingRad(#) + (caretW/2){-1, 1} + {0, -caretH}, {-1, 1}]& /@ bezierCirclePts["Q2"],
								(* Bottom left corner. *)
								Offset[roundingRad(#) + (caretW/2){-1, -1}, {-1, -1}]& /@ bezierCirclePts["Q3"],
								(* Bottom right corner *)
								Offset[roundingRad(#) + (caretW/2){1, -1}, {1, -1}]& /@ bezierCirclePts["Q4"][[;;-2]]
							]]]
						}, PlotRange -> 1, ImagePadding -> (roundingRad + caretW/2 + safetyPadding), AspectRatio -> Full],
					{width, height}],
			
				With[{contentsPadding = safetyPadding + roundingRad},
					(* Keep the contents within the area set by the frame's rounding radius and caret height. *)
					Pane[
						Style[contents, LineIndent -> 0],
						{width - 2contentsPadding, height - (2contentsPadding + caretH)},
						ImageMargins -> {
							{contentsPadding, contentsPadding},
							{contentsPadding, contentsPadding + caretH}},
					Alignment -> OptionValue[Alignment]]]},
					
			(* Allow user interactions in the contents pane. *)
			{1, 2}, 2,
			
			Alignment -> {Center, Center}]]


(* ::Section::Closed:: *)
(*Support Functions*)


(* cellManagement removes its evaluation cell if the kernel is quit. *)
cellManagement[expr_] :=
	DynamicModule[{kernelWasQuitQ = False, originalSessionID = $SessionID},
		DynamicWrapper[

			PaneSelector[{False -> Quiet[expr], True -> Spacer[0]},
				Dynamic[kernelWasQuitQ],
				ImageSize -> Automatic],

			If[kernelWasQuitQ,
				NotebookDelete[EvaluationCell[]]],

			SynchronousUpdating -> False],
		
		Initialization :> (kernelWasQuitQ = (originalSessionID =!= $SessionID))]


SetAttributes[noRecursion, HoldFirst]
noRecursion[expr_] := Dynamic[expr, UpdateInterval -> Infinity]


(*
	Ideally, all linting information generated by the UI would be stored in a structure of nested Associations.
	However, seeing as changing *any* value in an association causes *all* dynamically-tracked keys to trigger dynamic
	updates, we must instead generate hierarchical variable names in which to store and read linter information.
	These names take the form:
	CodeInspector`LinterUI`Private`Vars`$$[NotebookID]$$[CellID]$$[Name]
	and:
	CodeInspector`LinterUI`Private`Vars`$$[NotebookID]$$[CellID]$$[LintID]$$[Name]
*)


(* Functions to generate the variables name strings: *)


varNameString[notebook_NotebookObject] :=
	StringJoin[
		"CodeInspector`LinterUI`Private`Vars`",
		"$$",
		With[{nbID = Last[notebook]},
			(*For UUID nb objects, replace dashes with "$"s so they can be used in variable names.
				Otherwise, the nb object index is just a number, so convert it to a string. *)
			If[StringQ[nbID], StringReplace[nbID, "-" -> "$"], ToString[nbID]]],
		"$$"]


varNameString[cell_CellObject] :=
	StringJoin[
		(* Append the cell identifier to its parent notebook` variable name. *)
		(* Note that if the cell has been destroyed, then ParentNotebook[cell] returns $Failed. Therefore, fall back to using the Evaluation Notebook. *)
		varNameString[Replace[ParentNotebook[cell], $Failed -> EvaluationNotebook[]]],
		With[{cellID = First[cell]},
			(* As with nb objects, check if the cell object contains a UUID or a number. *)
			If[StringQ[cellID], StringReplace[cellID, "-" -> "$"], ToString[cellID]]],
		"$$"]


(* Notebook granularity is used for storing/updating information pertaining to the docked cell - such as
	if the docked cell is present, or if an analysis is in progress for that notebook. *)
varNameString[notebook_NotebookObject, name_String] :=
	(* Append the variable name to its parent notebook` name. *)
	StringJoin[varNameString[notebook], name]


(* Cell granularity is used for storing/updating information pertaining to an entire lint pod - such as
	the contents of the input/code cell, or the lint rafts for that pod. *)
varNameString[cell_CellObject, name_String] :=
	(* Append the variable name to its parent notebook`cell name. *)
	StringJoin[varNameString[cell], name]


(* Lint granularity is required because in-place lint rafts and mooring lint rafts need to communicate their states
	to each other. For example, when you hover over an in-place raft, the corresponding mooring raft changes color. *)

extractFirstList[expr_] := FirstCase[expr, _List, {}, {0, Infinity}]

varNameString[cell_CellObject, lint_CodeInspector`InspectionObject, name_String] :=
	StringJoin[
		varNameString[cell],
		(* Use the (first) lint source as the lint ID, such that e.g. {3, 11, 6} -> "3$11$6" *)
		Sequence @@ Riffle[ToString /@ extractFirstList[Last[lint][CodeParser`Source]], "$"],
		"$$",
		name]


(* Functions that apply a given function to the variable and given (optional) arguments: *)


applyToVar[function_, {cell_CellObject, name_String}, args___] :=
	function @@ Join[ToHeldExpression[varNameString[cell, name]], Hold[args]]


applyToVar[function_, {cellOrNB_ /; MatchQ[cellOrNB, _CellObject | _NotebookObject], All}, args___] :=
	Apply[function, Join[ToHeldExpression[#], Hold[args]]]& /@ Names[varNameString[cellOrNB] <> "*"]


applyToVar[function_, {notebook_NotebookObject, All, name_String}, args___] :=
	Apply[function, Join[ToHeldExpression[#], Hold[args]]]& /@ Names[varNameString[notebook] <> "*" <> name]


(* Set a variable to a value: *)


varSet[{cellOrNB_ /; MatchQ[cellOrNB, _CellObject | _NotebookObject], name_String}, value_] :=
	Set @@ Append[ToHeldExpression[varNameString[cellOrNB, name]], value]


varSet[{cell_CellObject, lint_CodeInspector`InspectionObject, name_String}, value_] :=
	Set @@ Append[ToHeldExpression[varNameString[cell, lint, name]], value]


(* Functions to query variable values: *)


varValue[cellOrNB_ /; MatchQ[cellOrNB, _CellObject | _NotebookObject], name_String] :=
	Symbol[varNameString[cellOrNB, name]]


varValue[cell_CellObject, lint_CodeInspector`InspectionObject, name_String] :=
	Symbol[varNameString[cell, lint, name]]


varValue[cellOrNB_ /; MatchQ[cellOrNB, _CellObject | _NotebookObject], All] :=
	Symbol /@ Names[varNameString[cellOrNB] <> "*"]


varValue[notebook_NotebookObject, All, name_String] :=
	Symbol /@ Names[varNameString[notebook] <> "*" <> name]


(* ---------- *)


applyChanges[cell_CellObject] := (
	NotebookWrite[
		cell,
		varValue[cell, "CellContents"],
		After];
		
	NotebookDelete /@ varValue[cell, "UIAttachedCells"])


(* ::Text:: *)
(*There are several large constructs that need to dynamically redraw when some value changes.*)
(*These values are stored in an association, so if we simply track a value from that association, then the construct will needlessly redraw when other values of that association change.*)
(*Therefore we wrap the construct in a DynamicWrapper that tracks the specific value of the association that we're interested in. The Dynamic construct then only updates with the tracker, and is thus prevented from needlessly refreshing when other values of the association change.*)


SetAttributes[isolatedDynamic, HoldRest]


isolatedDynamic[Dynamic[var_], expr_] :=
	DynamicModule[{tracker = var},
		DynamicWrapper[
			Dynamic[tracker; expr, TrackedSymbols :> {tracker}],
			If[tracker =!= var, tracker = var]]]


constrainWidth[expr_, width_:354] := Pane[Style[expr, LineIndent -> 0], width, FrameMargins -> 0, ImageMargins -> 0, BaselinePosition -> Baseline]


(* ::Section::Closed:: *)
(*Lint Rafts*)


constructRaftMenuItemLabel[raftType_, icon_, label_] :=
	Highlighted[
		Grid[
			{{icon, Spacer[7], constrainWidth[label, Full]}},
			ItemSize -> Automatic, Spacings -> 0, Alignment -> {Left, Top}],
		
		Frame -> None, RoundingRadius -> 0, FrameMargins -> {{5, 2}, {2, 2}},
		
		ImageSize -> Switch[raftType,
			"inPlace", 384,
			"mooring", Full],
			
		ImageMargins -> {{0, 0}, {0, 0}},
		
		(* Highlight the menu item on mouseover. *)
		Background -> Dynamic[If[CurrentValue["MouseOver"], colorData["RaftItemHighlight"], colorData["RaftMenuBack"]]]
	]


raftMenuItemClickAction[
	Dynamic[itemClicked_],
	raftType_, Dynamic[raftCell_], Dynamic[raftMenu_]
] := (
	(* itemClicked tells the parent raft whether this menu was deleted by a) clicking on a menu item (=True), or b) by mousing
		out or clicking the raft closer (=False). This is important for correctly setting the lint state variable to either "inactive"
		or "hoverXXXX" on menu dismissal. *)
	itemClicked = True;
			
	(* If the raft is in-place, then clicking a menu item should delete the entire raft. However, mooring rafts need to
		stay in the mooring when an action is taken, so only delete the raft menu. *)
	Switch[raftType,
		"inPlace", NotebookDelete[raftCell],
		"mooring", NotebookDelete[raftMenu]]
)


relintAndRemarkup[cell_CellObject, cellContents_] :=
	With[
		(* Regenerate the lints. *)
		(* Extract codeBoxes from the cell expression: Cell[BoxData[codeBoxes], ...] *)
		{codeBoxes = First[First[cellContents]]},
		(* Analyze the cell. *)
		{unfilteredLints = CodeInspector`CodeInspectBox[codeBoxes]},
		(* Filter the lints. *)
		{allLintsAndTheirSources = refineSources[unfilteredLints, cell, codeBoxes]},
		{lints = Through[allLintsAndTheirSources["Lint"]]},
		
		(* Re-markup. *)
		varSet[{cell, "MarkedUpCode"},
			Fold[
				Function[{markedUpCodeBoxes, oneLintAndItsSources},
					markupCode[cell, oneLintAndItsSources["Lint"], oneLintAndItsSources["Sources"], markedUpCodeBoxes]],
				(* Start with the BoxData contents of the cell... *)
				codeBoxes,
				(* ...and mark it up according to a lint, carrying the markup forwards to the next markup application. *)
				allLintsAndTheirSources]];
		
		(* Regenerate the rafts. *)
		varSet[{cell, "LintRafts"}, makeRaftCell[cell, #]& /@ lints];
		
		(* Update the list of InspectionObjects so that the Cell bracket button can update its lint counts. *)
		varSet[{cell, "Lints"}, lints]]


(* ::Subsection::Closed:: *)
(*"Ignore" Menu Item*)


makeTagOptionList[lint_CodeInspector`InspectionObject] :=
	With[
		{tagArgument = Lookup[lint[[4]], "Argument", Nothing]},
		Join[
			{CodeAssistOptions, "CodeToolsOptions", "CodeInspect", "Tags"},
			{lint["Tag"], tagArgument},
			{Enabled}]]


makeRaftMenuIgnoreItem[
	(* The scope at which you're suppressing the lint tag (CellObj, NbObj, or $FrontEnd). *)
	scope_,
	(* The linted input/code cell. *)
	cell_CellObject,
	(* The menu item icon. *)
	icon_,
	(* The menu item label, of the form {"Text", #, "more text."}&, where # is the lint tag that gets filled in from... *)
	label_Function,
	(* ...the lint, which is also used to set the suppression option value. *)
	lint_CodeInspector`InspectionObject,
	(* itemClicked is a state variable. *)
	Dynamic[itemClicked_],
	(* These raftXXXX vars are used to delete raft components when a menu item is clicked. *)
	raftType_, Dynamic[raftCell_], Dynamic[raftMenu_]
] := 
	With[{argument = Last[lint]["Argument"]},
		Button[
		
			(* ----- The menu item label ----- *)
			
			constructRaftMenuItemLabel[raftType, icon,
				(* The lint tag is drawn in a different color to the rest of the item.
					If it has an "Argument", then display LintTag\:25bbArgument. *)
				styleData["RaftMenuItem"][Row[label[
					Style[If[argument === Missing["KeyAbsent", "Argument"],
						lint["Tag"],
						Row[{lint["Tag"], "\:25bb", argument}, "\[VeryThinSpace]"]], FontColor -> colorData["UIDark"]]]]]],
			
			
			(* ----- The menu item action ----- *)
			
			(* Set the Enabled option for tag to False. *)
			(* CurrentValue is used to set the option, but AbsoluteCurrentValue is used to query the option to ensure correct
				scope inheritance. See https://stash.wolfram.com/projects/FE/repos/frontend/pull-requests/5783/overview *)
			CurrentValue[scope, makeTagOptionList[lint]] = False;
			
			(* Now re-lint, and re-markup the code. *)
			relintAndRemarkup[cell, varValue[cell, "CellContents"]];
			
			(* Set state variables and delete raft components. *)
			raftMenuItemClickAction[Dynamic[itemClicked], raftType, Dynamic[raftCell], Dynamic[raftMenu]],
				
			Appearance -> None]]


(* ::Subsection::Closed:: *)
(*CodeAction Menu Item*)


makeRaftMenuCodeActionItem[
	cell_CellObject,
	codeAction_CodeParser`CodeAction,
	(* itemClicked is a state variable. *)
	Dynamic[itemClicked_],
	(* These raftXXXX vars are used to delete raft components when a menu item is clicked. *)
	raftType_, Dynamic[raftCell_], Dynamic[raftMenu_]
] := 
	With[{},
		Button[
		
			(* ----- The menu item label ----- *)
			
			constructRaftMenuItemLabel[raftType, 
			
				(* The action icon. *)
				iconData["Wand"][colorData["UIDark"]],
				
				(* The code action label. Format pieces of code within the label with "Input" style, and enforce "StandardForm"'s font. *)
					styleData["RaftMenuItem"][Row[
						(* I'm using Brenton's boldify function to do the string-parsing, and then replacing his formatting wrappers with StyleBoxes. *)
						Replace[CodeInspector`Utils`boldify[codeAction["Label"]],
							CodeInspector`Format`LintMarkup[s_, ___] :>
								RawBoxes[inputStyle[s, FontFamily ->
									CurrentValue[{StyleDefinitions, "StandardForm", FontFamily}]]],
							1]]]],
			
			
			(* ----- The menu item action ----- *)
			
			(* Perform the codeAction transformation on the cell contents. This involves converting into, and back out of, Brenton's concrete syntax tree. *)
			varSet[{cell, "CellContents"},
				ReplacePart[
					varValue[cell, "CellContents"],
					(* cellContents is of the form Cell[BoxData[_], ___], so we want to apply the code actions on the contents of BoxData. *)
					{1, 1} -> CodeParser`ToStandardFormBoxes @ CodeParser`CodeAction`ApplyCodeAction[
						codeAction,
						CodeParser`CodeConcreteParseBox[First[First[varValue[cell, "CellContents"]]]]]]];
			
			(* Now re-lint, and re-markup the code. *)
			relintAndRemarkup[cell, varValue[cell, "CellContents"]];
			
			(* Inform the rest of the UI that an edit has been made. *)
			varSet[{cell, "EditsMadeQ"}, True];
			
			(* Set state variables and delete raft components. *)
			raftMenuItemClickAction[Dynamic[itemClicked], raftType, Dynamic[raftCell], Dynamic[raftMenu]],
				
			Appearance -> None]]


(* ::Subsection::Closed:: *)
(*Docs Menu Item*)


makeRaftMenuDocsItem[
	{
		(* Link to the symbol doc page... *)
		symbol_String,
		(* ...but show display (symbol and display have been separated becuase you may want to display "&" but link to Function) *)
		display_String
	},
	(* itemClicked is a state variable. *)
	Dynamic[itemClicked_],
	(* These raftXXXX vars are used to delete raft components when a menu item is clicked. *)
	raftType_, Dynamic[raftCell_], Dynamic[raftMenu_]
] := 
	With[{},
		Button[
			(* ----- The menu item label ----- *)
			
			(* The label is the docs info icon, followed by the name of the symbol, and an open-link arrow icon. *)
			constructRaftMenuItemLabel[raftType,
				iconData["Info"][colorData["UIDark"]],
				styleData["RaftMenuItem"][Row[CodeInspector`Utils`boldify["``" <> display <> "``"]]]],
			
			
			(* ----- The menu item action ----- *)
			
			(* Open the documentation page for the symbol. *)
			SystemOpen[URLBuild[{"paclet:ref", symbol}]];
			
			(* Set state variables and delete raft components. *)
			raftMenuItemClickAction[Dynamic[itemClicked], raftType, Dynamic[raftCell], Dynamic[raftMenu]],
				
			Appearance -> None]]


(* ::Subsection::Closed:: *)
(*Construct Raft*)


(* We want to include docs items for the operator input forms of functions.
	This list is not a complete set of operator input forms, but it is a useful subset for code analysis. *)
$operatorForms = {
	"%" -> "Out",
	"%%" -> "Out",
	"%%%" -> "Out",
	"#" -> "Slot",
	"_" -> "Blank",
	"__" -> "BlankSequence",
	"___" -> "BlankNullSequence",
	"<<" -> "Get",
	"?" -> "PatternTest",
	"@" -> "Prefix",
	"@@" -> "Apply",
	"@@@" -> "Apply",
	"/@" -> "Map",
	"//@" -> "MapAll",
	"@*" -> "Composition",
	"/*" -> "RightComposition",
	"~" -> "Infix",
	"&" -> "Function",
	"->" -> "Rule",
	"\[Rule]" -> "Rule",
	";;" -> "Span",
	"==" -> "Equal",
	"\[Equal]" -> "Equal",
	"!=" -> "Unequal",
	"\[NotEqual]" -> "Unequal",
	">" -> "Greater",
	">=" -> "GreaterEqual",
	"\[GreaterEqual]" -> "GreaterEqual",
	"\[GreaterSlantEqual]" -> "GreaterEqual",
	"<" -> "Less",
	"<=" -> "LessEqual",
	"\[LessEqual]" -> "LessEqual",
	"\[LessSlantEqual]" -> "LessEqual",
	"|" -> "Alternatives",
	"===" -> "SameQ",
	"=!=" -> "UnsameQ",
	"\[Element]" -> "Element",
	"\[NotElement]" -> "NotElement",
	"\[Subset]" -> "Subset",
	"\[Superset]" -> "Superset",
	"\[ForAll]" -> "ForAll",
	"\[Exists]" -> "Exists",
	"\[NotExists]" -> "NotExists",
	"!" -> "Not",
	"\[Not]" -> "Not",
	"&&" -> "And",
	"\[And]" -> "And",
	"\[Nand]" -> "Nand",
	"\[Xor]" -> "Xor",
	"\[Xnor]" -> "Xnor",
	"||" -> "Or",
	"\[Or]" -> "Or",
	"\[Nor]" -> "Nor",
	"\[Equivalent]" -> "Equivalent",
	"\[Implies]" -> "Implies",
	"\[RoundImplies]" -> "Implies",
	".." -> "Repeated",
	"..." -> "RepeatedNull",
	"~~" -> "StringExpression",
	"/;" -> "Condition",
	"/." -> "ReplaceAll",
	"//." -> "ReplaceRepeated",
	":>" -> "RuleDelayed",
	"\[RuleDelayed]" -> "RuleDelayed",
	"+=" -> "AddTo",
	"-=" -> "SubtractFrom",
	"*=" -> "TimesBy",
	"/=" -> "DivideBy",
	"//=" -> "ApplyTo",
	"//" -> "Postfix",
	"\[Therefore]" -> "Therefore",
	"\[Because]" -> "Because",
	"=" -> "Set",
	":=" -> "SetDelayed",
	"^=" -> "UpSet",
	"^:=" -> "UpSetDelayed",
	"/:" -> "TagSet",
	"=." -> "Unset",
	"|->" -> "Function",
	"\[Function]" -> "Function",
	">>" -> "Put",
	">>>" -> "PutAppend",
	";" -> "CompoundExpression",
	"\[DifferentialD]" -> "DifferentialD",
	"'" -> "Derivative",
	"''" -> "Derivative",
	"'''" -> "Derivative",
	"\[PartialD]" -> "D",
	"\[Del]" -> "Del",
	"**" -> "NonCommutativeMultiply",
	"\[Cross]" -> "Cross",
	"." -> "Dot"
};


makeRaftMenu[cell_CellObject, lint_CodeInspector`InspectionObject, raftCell_CellObject, Dynamic[itemClicked_], raftType_, Dynamic[raftMenu_]] :=
	With[
		{
			(* Don't provide documentation links for these symbols. *)
			excludedDocsSymbols = {"List"}
		},

		{
			(* Get any system symbols that have been marked up as "``XXXX``" in the description. We'll provide docs refs for these. *)
			symbols = DeleteDuplicates @ DeleteCases[
				StringCases[lint["Description"],
					"``" ~~ s__ ~~ "``" /; Or[
						MatchQ[s, Alternatives @@ $operatorForms[[All, 1]]],
						StringFreeQ[s, " "] && Quiet[Context[s]] === "System`"] :> s],
				Alternatives @@ excludedDocsSymbols]
		},
		
		{
			(* Transform "symbol" into {"symbol", "display"}, so that makeRaftMenuDocsItem knows what to display, and which symbol to link to. *)
			symbolsAndDisplays = {Replace[#, $operatorForms], #}& /@ symbols,
			
			(* Get the list of code actions from the lint, if they exist. *)
			actionItems = Lookup[lint[[4]], CodeParser`CodeActions, {}],
			
			(* Define the "Ignore" item buttons. *)
			ignoreItems = {
			
				(* Cell *)
				makeRaftMenuIgnoreItem[cell, cell, iconData["IgnoreInCell"][colorData["UIDark"]],
					{"Ignore ", #, " errors in this cell"}&,
					lint, Dynamic[itemClicked], raftType, Dynamic[raftCell], Dynamic[raftMenu]],
					
				(* Notebook *)
				makeRaftMenuIgnoreItem[ParentNotebook[cell], cell, iconData["IgnoreInNotebook"][colorData["UIDark"]],
					{"Ignore ", #, " errors in this notebook"}&,
					lint, Dynamic[itemClicked], raftType, Dynamic[raftCell], Dynamic[raftMenu]],
					
				(* init.m *)
				makeRaftMenuIgnoreItem[$FrontEnd, cell, iconData["IgnoreAlways"][colorData["UIDark"]],
					{"Ignore ", #, " errors always"}&,
					lint, Dynamic[itemClicked], raftType, Dynamic[raftCell], Dynamic[raftMenu]]
			},
			
			delimiter = Graphics[
				{CapForm["Round"], colorData["RaftDelimiter"], AbsoluteThickness[1],
					Line[{{-1, 0}, {1, 0}}]},
				AspectRatio -> Full, PlotRange -> {{-1, 1}, {-1, 1}}, ImageMargins -> {{0, 0}, 2{1, 1}},
				ImagePadding -> {5{1, 1}, {0, 0}}, ImageSize -> {Full, 2}],
			
			(* For a mooring lint pod:
				The drop down menu's size is affected by the input/code cell's margins. The menu's ImageSize is Full so that it resizes
				with the window width, but this means that it spills over the window edge seeing as it's drawn inside the lint pod and
				therefore doesn't start at the very LHS of the window. Therefore we must subract the LHS and RHS cell margins from its
				width so that it fits in the pod properly. *)
			sumOfCellHMargins = Replace[Total[varValue[cell, "HMargins"]], Except[_?NumberQ] -> 70],
			
			hMarginsFudgeFactor = 14.5
		},
		
		
		Highlighted[
			Column[
				Flatten @ Riffle[
					Replace[{
						(* Code actions. *)
						Function[codeAction,
							makeRaftMenuCodeActionItem[
								cell,
								codeAction,
								Dynamic[itemClicked],
								raftType, Dynamic[raftCell], Dynamic[raftMenu]]] /@ actionItems,
						
						(* Documentation links. *)
						Function[symbolAndDisplay,
							makeRaftMenuDocsItem[symbolAndDisplay, Dynamic[itemClicked],
								raftType, Dynamic[raftCell], Dynamic[raftMenu]]] /@ symbolsAndDisplays,
						
						(* Ingore-lint actions. *)
						ignoreItems
					
					(* Replace {} with Nothing so that you don't get delimiters between empty sections. *)
					}, {} -> Nothing, 1],
					
					(* Include delimiters between the action, docu, and ignore sections. *)
					constrainWidth[delimiter,
						Switch[raftType,
							"inPlace", 383,
							"mooring", Full]]
				],
				ItemSize -> {Full, 0}, Spacings -> 0, Alignment -> Left],
			
			Frame -> True, FrameMargins -> 3, Background -> White, RoundingRadius -> 0,
			FrameStyle -> Directive[AbsoluteThickness[1], colorData["RaftFrame"]],
			ImageMargins -> {
				Switch[raftType,
					"inPlace", {0, 0},
					"mooring", {0, sumOfCellHMargins + hMarginsFudgeFactor}],
				{0, 0}}
		]
	]


makeRaftCell[cell_CellObject, lint_CodeInspector`InspectionObject] :=
	With[
		{
			(* Construct the lint raft symbol. *)
			head = ToHeldExpression[varNameString[cell, lint, "Raft"]],
			
			lintDescription = lint["Description"]
		},
		
		(* Store the raft in a down value of the raft symbol, with an argument to specify whether
			the raft is in-place or in the mooring, and an optional Deinitialization argument. *)
		Block[{deinit, raftType},
			SetDelayed @@ Join[
				
				Replace[head, head_ :>
					head[raftType_?(MatchQ["mooring" | "inPlace"]), deinit:(Deinitialization :> _):{None}],
					1],
				
				(* --------------- The raft cell. --------------- *)
				Hold[
					(* The raftType argument is necessary for specifying if the
						lint state variable is set to "hoverMooring" or "hoverInPlace". *)
					With[{hoverValue = Switch[raftType, "mooring", "hoverMooring", "inPlace", "hoverInPlace"]},
						Cell[BoxData @ ToBoxes @
							DynamicModule[{raftOpenQ = False, mouseOver = False, raftMenu, itemClicked = False},
								DynamicWrapper[
									Button[
										Highlighted[
											Grid[
												(* --- The raft label contains: --- *)
												{{
													(* an exclamation icon, colored according to the lint severity, *)
													iconData["Exclam"][colorData[lint["Severity"]]], Spacer[6],
													(* a description of the lint. This underlines on mouseover, and has a double chevron icon at the end of it. *)
													constrainWidth[
														Style[
															Row[
																{
																	(* Incase the lint description contains any newlines, or it has line-wrapped, we need to add its words
																		individually to the Row, otherwise the "TakeAction" icon will appear floating to the right. Rather, we
																		want it to appear directly after the last word, as if it were itself a character. *)
																	Splice[styleData["RaftLabel"] /@ StringSplit[lintDescription]],
																	iconData["TakeAction"][colorData["RaftLabel"]]},
																" ",
																BaselinePosition -> Baseline],
															FontVariations -> {"Underline" -> Dynamic[mouseOver]}],
															
														Switch[raftType,
															"inPlace", UpTo[340],
															"mooring", Full,
															_, UpTo[340]]]}},
															
												ItemSize -> Automatic, Spacings -> {0, 0}, Alignment -> {Left, Baseline},
												BaselinePosition -> Scaled[.05]],
											
											(* --- Raft appearance options: --- *)

											Alignment -> {Center, Baseline},
											FrameMargins -> {{5, 5}, {3, 3}}, RoundingRadius -> 1,
											Frame -> True,

											(* Show the frame if the raft is in-place. For mooring rafts, only show the frame if the raft is active or hovered, or if the in-place raft is hovered. *)
											FrameStyle -> Switch[raftType,
												"inPlace",
												Directive[colorData["RaftFrame"], AbsoluteThickness[1]],
												"mooring",
												Dynamic[Switch[varValue[cell, lint, "State"],
													"active" | "hoverInPlace" | "hoverMooring", Directive[colorData["RaftFrame"], AbsoluteThickness[1]],
													_, Directive[colorData["UIBack"], AbsoluteThickness[1]]]]],

											(* Change the background according to the lint state. *)
											Background -> Dynamic[Switch[
												varValue[cell, lint, "State"],
												"inactive", Switch[raftType, "inPlace", colorData["RaftBack"], "mooring", colorData["UIBack"]],
												"active", colorData["RaftBackOpen"],
												"hoverMooring", colorData["RaftBackHover"],
												(* If the raft type is in-place, then the "hoverInPlace" value could be due to the mouse hovering over the linted boxes, but
													not the raft itself - so we must check to see if the raft is actually hovered over before highlighting its background. *)
												"hoverInPlace",
												Which[
													raftType === "mooring", colorData["RaftBackHover"],
													raftType === "inPlace" && mouseOver, colorData["RaftBackHover"],
													True, colorData["RaftBack"]],
												(* Failsafe. *)
												_, colorData["RaftBack"]]]],
										
										
										(* ----- Button action. ----- *)
										
										(* If there isn't already a raft menu, attach one. *)
										If[!raftOpenQ,
											raftMenu = AttachCell[EvaluationBox[],
											
												ExpressionCell[
													DynamicModule[{},
														makeRaftMenu[cell, lint, ParentCell[EvaluationBox[]], Dynamic[itemClicked], raftType, Dynamic[raftMenu]],
														Initialization :> (1),
														(* Upon closure of the menu, set the lint state to "inactive". This is correct if the mouse has moved out of the menu/raft.
															However, if the menu has been closed by clicking the raft itself, then the state *should* be set to "hoverXXXX" - but this
															happens by the raft's button action after the menu's Deinitialization fires. (In other words, it's fine to potentially
															incorrectly set the lint state to "inactive" here, becuase it will immediately corrected by the raft's button action.) *)
														Deinitialization :> (raftOpenQ = False; varSet[{cell, lint, "State"}, "inactive"])]],
												
												{Left, Bottom}, Offset[{0, 2}, 0], {Left, Top},
												RemovalConditions -> {"MouseExit"}]];
										
										(* If the raft is already open, and its label is clicked, then close the existing raft menu.
											If not, set the state variables to the open/active states. *)
										If[raftOpenQ,
											NotebookDelete[raftMenu]; varSet[{cell, lint, "State"}, hoverValue],
											varSet[{cell, lint, "State"}, "active"]; raftOpenQ = True],
												
										Appearance -> None],
									
									
									(* ----- DynamicWrapper action ----- *)
									
									(* Set the lint state to "hoverMooring" or "hoverInPlace" on mouseover (if the lint state is not already "active"). *)
									mouseOver = CurrentValue["MouseOver"];
									If[!MatchQ[varValue[cell, lint, "State"], "active" | "hoverInPlace"],
										varSet[{cell, lint, "State"}, If[mouseOver, hoverValue, "inactive"]]],
									
									(* This only needs to update with CurrentValue["MouseOver"]. *)
									TrackedSymbols :> {}],
								
								Initialization :> (1),		
								Deinitialization :> Last[deinit]]]]]]];
		
		(* Return the raft symbol. *)
		ReleaseHold[head]
	]


(* ::Section::Closed:: *)
(*Lint Pod*)


$linterPodCellHMargins = {5, 5};


(* ::Subsection::Closed:: *)
(*Title Bar*)


confirmClosurePopup[cell_CellObject, Dynamic[popupPresentQ_]] :=
	Cell[BoxData @ ToBoxes @
		DynamicModule[{},
			popupPane[
				(* Draw a button to discard the changes, and a button to apply them. Style the "discard" button with red text. *)
				Row[{
					button[Style["Discard Edits", colorData["WarningText"]],
						NotebookDelete /@ varValue[cell, "UIAttachedCells"]],
					button[Style["Apply Edits"],
						applyChanges[cell]]}, Spacer[5]],
				{225, 57},
				1,
				Alignment -> {Center, Center}],
				
			(* popupPresentQ is used to stop multiple confirmation popup cells being
				attached if the user clicks on the "close" button multiple times. *)
			Initialization :> (1),
			Deinitialization :> (popupPresentQ = False)]]


applyChangesButton[cell_CellObject] :=
	button[
		Function[col,
			Style[Row[{"Apply Edits", Spacer[3], iconData["Wand"][col]}, Alignment -> Baseline], 13],
			HoldAll],
			
		If[varValue[cell, "EditsMadeQ"], applyChanges[cell]],
		
		"ActiveQ" :> TrueQ[varValue[cell, "EditsMadeQ"]],
		"TextColor" -> colorData["ApplyButtonText"],
		"TextHoverColor" -> colorData["ApplyButtonText"],
		"BackColor" -> colorData["ApplyButtonBack"],
		"BackHoverColor" -> colorData["ApplyButtonBackHover"],
		"EdgeColor" -> colorData["ApplyButtonEdge"],
		"EdgeHoverColor" -> colorData["ApplyButtonEdge"],
		ImageSize -> {Automatic, 17}, Alignment -> {Center, Scaled[-.05]}]


titleBar[cell_CellObject] := 
	With[
		{
			roundingRad = $UIRoundingRadius - 1,
			hMarginsFudgeFactor = {0, 0}
		},
			
			DynamicModule[{popupPresentQ = False},
				Graphics[
					{
						(* Draw the title bar background - a rectangle with its top corners rounded. *)
						colorData["UIBack"],
						FilledCurve[BezierCurve[Join[
							(* Top right corner. *)
							Offset[roundingRad(# + {-1, -1}), {1, 1}]& /@ bezierCirclePts["Q1"][[2;;]],
							(* Top left corner. *)
							Offset[roundingRad(# + {1, -1}), {-1, 1}]& /@ bezierCirclePts["Q2"],
							(* Bottom left corner. *)
							Table[{-1, -1}, 3],
							(* Bottom right corner *)
							Table[{1, -1}, 2]]]],
						
						(* The left-aligned pod title. *)
						Text[styleData["SectionHeader"]["Code Analysis", FontColor->colorData["UIDark"]], Offset[{8, 0}, {-1, 0}], {-1, 0}],
						
						(* Draw a "close" button at the right of the title bar. *)
						Button[
							Tooltip[closeIcon[{-13, -10}, {1, 1}], "Close analysis pod", TooltipDelay -> 0],
							
							(* If edits have been made, attach the closure confirmation popup. Otherwise, just delete the lint pod cells. *)
							If[
								TrueQ[varValue[cell, "EditsMadeQ"]],
								
								(* Only attach the popup if there isn't one already present. *)
								If[!popupPresentQ,
									popupPresentQ = True;
									AttachCell[
										EvaluationBox[],
										confirmClosurePopup[
											cell,
											Dynamic[popupPresentQ]],
										{Right, Bottom}, Offset[{6, 2}, Automatic], {Right, Top},
										RemovalConditions -> {"MouseClickOutside"}]],
									
								NotebookDelete /@ varValue[cell, "UIAttachedCells"]]]},
										
					AspectRatio -> Full, ImageSize -> {Full, 20}, PlotRange -> {{-1, 1}, {-1, 1}}, ImageMargins -> {{0, 0}, {0, 0}},
					ImagePadding -> {hMarginsFudgeFactor, {0, 0}}]]]


(* ::Subsection::Closed:: *)
(*Code Pane*)


codePane[cell_CellObject, cellType_] :=
	With[
		{
			hMarginsFudgeFactor = {0, 0},
			vContentPadding = {5, 4},
			hContentPadding = {0, 0}
		},
		
		Highlighted[
			Pane[
				Pane[
					Dynamic[
						(* Due to bug 407314, we can't just wrap the marked-up boxes in Cell[BoxData[]] seeing as the rafts prematurely disappear,
							so we instead ensure that the marked-up code is a list, and then wrap it in RowBox. *)
						With[{boxes = Flatten@List[varValue[cell, "MarkedUpCode"]]},
							(* Style the boxes as input code. *)
							RawBoxes[inputStyle[RowBox[boxes]]]]],
					
					(* The inner Pane width is Full for "Input" cells, and the width of the cell for "Code" cells. *)
					ImageSize -> Switch[cellType,
						{"Input"}, {Full, Full},
						(* Note that a code cell is only going to change width if its contents has changed, and in that case the
							whole lint pod needs to be recalculated. So it is not necessary to dynamically track the cell width. *)
						{"Code"}, {varValue[cell, "Width"], Full},
						(* Failsafe *)
						_, {Full, Full}],
					Alignment -> {Left, Top}, FrameMargins -> None, ImageMargins -> {{0, 0}, {0, 0}}, ContentPadding -> False, ImageSizeAction -> "Clip"],
				
				(* The outer Pane width is Full, and will scroll if the inner pane is wider than the notebook window (i.e. in the case of a "Code" cell). *)
				Full,
				
				FrameMargins -> {{0, 0}, {0, 0}},
				ImageMargins -> {{0, 0}, {0, 0}},
				Alignment -> {Left, Top}, ImageSizeAction -> "Scrollable", AppearanceElements -> None, Scrollbars -> {Automatic, False}, ContentPadding -> False],
				
			Background -> colorData["CodeBack"], RoundingRadius -> 0, ContentPadding -> False,
			ImageMargins -> {hMarginsFudgeFactor, {0, 0}},
			FrameMargins -> {$linterPodCellHMargins + hContentPadding, vContentPadding},
			Frame -> True, FrameStyle -> Directive[colorData["UIBack"], AbsoluteThickness[1]]]]


(* ::Subsection::Closed:: *)
(*Mooring*)


mooring[cell_CellObject, Dynamic[showAllQ_], minRafts_] :=
	With[
		{
			hMarginsFudgeFactor = {0, 0},
			vContentPadding = {0, 0},
			raftColumn = Function[list,
				Column[list, ItemSize -> {0, 0}, Spacings -> 0, Alignment -> Left]]
		},
		
		Highlighted[
			Dynamic[
				PaneSelector[
					{
						(* Show a column of only minRafts in the condensed (default) view. *)
						False -> raftColumn[
							RawBoxes /@ Part[
								Through[varValue[cell, "LintRafts"]["mooring"]],
								;; UpTo[minRafts], 1, 1]],
					
						(* Show a column of all the rafts in the expanded view. *)
						True -> raftColumn[
							RawBoxes /@ Part[
								Through[varValue[cell, "LintRafts"]["mooring"]],
								All, 1, 1]]},
					
					(* Show the expanded view if showAllQ has been set to True by the "Show All" button, or if the total
						number of rafts is less than or equal to minRafts. *)
					Dynamic[Or[
						showAllQ,
						TrueQ[Length[varValue[cell, "LintRafts"]] <= minRafts]]],
					
					ImageSize -> Automatic]],
			
			
			Background -> colorData["UIBack"], RoundingRadius -> 0, ImageSize -> Full,
			FrameMargins -> {$linterPodCellHMargins, vContentPadding},
			ImageMargins -> {hMarginsFudgeFactor, {0, 0}}]]


(* ::Subsection::Closed:: *)
(*Footer Bar*)


showAllButton[Dynamic[showAllQ_]] :=
With[{formatIcon = Function[Show[#, ImageSize -> {13, 9}, BaselinePosition -> Scaled[.1]]]},
	button[
		(* Switch between "Show All v" and "Show Fewer ^" depending on showAllQ. *)
		PaneSelector[
			{
				False -> Style[Row[{"Show All", " ", formatIcon[iconData["DownChevron"][colorData["UIDark"]]]}], 12],
				True -> Style[Row[{"Show Fewer", " ", formatIcon[iconData["UpChevron"][colorData["UIDark"]]]}], 12]
			},
			Dynamic[showAllQ],
			ImageSize -> Automatic, ImageMargins -> 0, FrameMargins -> None, BaselinePosition -> (*(Scaled[0] -> Baseline)*)Baseline],

		(* The action of this button is just to toggle showAllQ. *)
		showAllQ = !TrueQ[showAllQ],

		ImageSize -> {Automatic, 16}, BaselinePosition -> (*(Scaled[.25] -> Baseline)*)Baseline, Alignment -> {Center, Scaled[-.05]}]]


footerBar[cell_CellObject, Dynamic[showAllQ_], minRafts_] :=
	With[
		{
			roundingRad = $UIRoundingRadius - 1,
			hMarginsFudgeFactor = {0, 0},
			footerHeight = 21
		},
		
		DynamicModule[{raftCount = 0},
			DynamicWrapper[
				Graphics[
					{
						colorData["UIBack"],
						FilledCurve[BezierCurve[Join[
							(* Top left corner. *)
							Table[{-1, 1}, 2],
							(* Bottom left corner. *)
							Offset[roundingRad(# + {1, 1}), {-1, -1}]& /@ bezierCirclePts["Q3"],
							(* Bottom right corner. *)
							Offset[roundingRad(# + {-1, 1}), {1, -1}]& /@ bezierCirclePts["Q4"],
							(* Top right corner *)
							Table[{1, 1}, 2]]]],
						
						(* The "Apply Edits" button. *)
						Inset[applyChangesButton[cell], Offset[{-6, 1}, {1, 0}], {1, 0}],
						
						(* The lint count and "Show All" button. *)
						Inset[
							Row[styleData["FooterText"] /@ {

								Spacer[3],
								
								(* Lint count. *)
								Dynamic[If[TrueQ[raftCount > minRafts], "Showing ", ""]],
								Dynamic[If[showAllQ, raftCount, Clip[raftCount, {0, minRafts}]]],
								" ",
								Dynamic[Which[
									raftCount === 1, "issue found",
									raftCount === 2 || raftCount === 0, "issues found",
									True,
									Row[{"of", " ", raftCount, " ", "issues found"}]]],
								".",

								Spacer[5],
								
								(* Only display the "Show All" button if the raft count is more than minRafts. *)
								PaneSelector[
									{True -> Spacer[0],
										False -> showAllButton[Dynamic[showAllQ]]},
											
									Dynamic[TrueQ[raftCount <= minRafts]],
									
									ImageSize -> Automatic, ImageMargins -> 0, FrameMargins -> None]
							}],
								
							Offset[{5, 2.5}, {-1, 0}], {-1, 0}]
					},
									
					AspectRatio -> Full, ImageSize -> {Full, footerHeight}, PlotRange -> {{-1, 1}, {-1, 1}},
					ImageMargins -> {{0, 0}, {0, 0}},
					ImagePadding -> {hMarginsFudgeFactor, {0, 0}}],
				
				raftCount = Length[varValue[cell, "LintRafts"]]]]]


(* ::Subsection::Closed:: *)
(*Hash-Changed Overlay*)


hashChangedOverlayReanalyzeButton[cell_CellObject] :=
	button["Reanalyze",
		With[{evalCell = EvaluationCell[]}, SessionSubmit[ScheduledTask[NotebookDelete[evalCell], .1]]];
		attachAnalysisAction[{cell}],
		ImageSize -> {98, 19},
		Method -> "Queued"]


hashChangedOverlayClosePodButton[cell_CellObject] :=
	button["Close",
		NotebookDelete /@ varValue[cell, "UIAttachedCells"],
		ImageSize -> {98, 19}]


hashChangedOverlay[cell_, Dynamic[overlayAttachedQ_]] :=
	Pane[
		DynamicModule[{},
			DynamicWrapper[
				Column[{
					styleData["SectionHeader"]["The cell contents have changed."],
					Row[
						{
							hashChangedOverlayClosePodButton[cell],
							hashChangedOverlayReanalyzeButton[cell]},
						Spacer[10]]
				}, Spacings -> 1.1, Alignment -> Center],

				(* If the changes to the input/code cell have been reversed, delete this overlay. *)
				If[varValue[cell, "hashChangedQ"] === False,
					With[{evalCell = EvaluationCell[]}, SessionSubmit[ScheduledTask[NotebookDelete[evalCell], .1]]]]],

			Initialization :> (overlayAttachedQ = True),
			Deinitialization :> (overlayAttachedQ = False)],
		ImageMargins -> {{0, 0}, {0, 16}}]


(* ::Subsection::Closed:: *)
(*Assembled Pod*)


lintPod[cell_CellObject, cellType_] :=
	With[
		{
			delimiter = Function[vMargins, Graphics[
				{AbsoluteThickness[1], colorData["Delimiter"], CapForm["Round"],
					Line[{{-1, 0}, {1, 0}}]},
				AspectRatio -> Full, PlotRange -> {{-1, 1}, {-1, 1}}, ImagePadding -> {(*10{1, 1}*){0, 0}, {0, 0}}, ImageSize -> {Full, 2},
				BaselinePosition -> Scaled[.1], ImageMargins -> {{0, 0}, vMargins}]],
			minRafts = 2
		},
		
		DynamicModule[{showAllQ = False, cellHashChangedQ = False, overlayAttachedQ = False, uiCellSize},
			With[
				{contents =
					(* Stack all the lint pod components *)
					noRecursion @ Column[
						{
							titleBar[cell],
							delimiter[{4, 0}],
							codePane[cell, cellType],
							delimiter[{5, 4}],
							mooring[cell, Dynamic[showAllQ], minRafts],
							delimiter[{4, 5}],
							footerBar[cell, Dynamic[showAllQ], minRafts]},
						ItemSize -> {Full, 0}, Spacings -> 0]},
				
				DynamicWrapper[
					(* Use an overlay to grey out and disable the lint pod when the cell's cryptohash changes. *)
					noRecursion @ Overlay[
						{
							(* The main linting pod body. *)
							Highlighted[contents,
								(* Add a border around the lint pod. *)
								RoundingRadius -> $UIRoundingRadius, Background -> colorData["UIBack"], FrameMargins -> 0,
								Frame -> True, FrameStyle -> Directive[AbsoluteThickness[1], colorData["UIEdge"]], 
								ImageMargins -> {$linterPodCellHMargins, {0, 0}}],
							
							(* The grey overlay layer. *)
							Highlighted[Spacer[0],
								RoundingRadius -> $UIRoundingRadius,
								Background -> Opacity[.8, colorData["UIBack"]],
								ImageSize -> Dynamic[{Full, Last[uiCellSize] - 2(*fudge factor*)}]]
						},
						
						Dynamic[If[cellHashChangedQ, {1, 2}, {1}]],
						(* Deactivate the lint pod if the cell hash has changed. *)
						Dynamic[If[cellHashChangedQ, None, 1]],
						Alignment -> {Center, Top}],
					
					With[
						(* Check if the original input/output cell has been modified. *)
						{changedQ = FrontEndExecute[FrontEnd`CryptoHash[cell]][[2, -1]] =!= varValue[cell, "Hash"]},
						If[cellHashChangedQ =!= changedQ,
							cellHashChangedQ = changedQ;
							varSet[{cell, "hashChangedQ"}, changedQ];
							(* If the cell hash has changed and there isn't already an overlay cell present, then attach the cell-has-changed overlay. *)
							If[changedQ && !overlayAttachedQ,
								(* Get the ImageSize of this UI cell to ensure the grey-out Overlay layer is displayed the correct size. *)
								uiCellSize = AbsoluteCurrentValue[EvaluationCell[], CellSize];
								AttachCell[
									EvaluationCell[],
									hashChangedOverlay[cell, Dynamic[overlayAttachedQ]],
									{Center, Top}, {0, 0}, {Center, Top}]]]]]],
			
			Initialization :> (1),
			Deinitialization :> (
				(* Delete the cell bracket button. *)
				Quiet[NotebookDelete[First[varValue[cell, "UIAttachedCells"]]]];
				
				(* Clear the severity counts when the lint pod is removed. *)
				(* This line is very important. Seeing as varValue is using Names["XXXX*"] to find groups of symbols, the
					symbols for this cell must be completely Removed when the cell is deleted, otherwise varValue could return ghost symbols. *)
				applyToVar[Remove, {cell, All}];
				
				(* Tickle the docked cell. *)
				CodeInspector`LinterUI`Private`DynamicTriggers`dockedCellLintCounts = RandomReal[])]]


(* ::Section::Closed:: *)
(*Cell Bracket Button*)


(* ::Text:: *)
(*cellBracketButton is a small button that is drawn at the top of a linted cell's bracket. It displays a row of the lint severity counts for the cell, with color-keyed exclam icons. If there are no lints, it displays a tick. *)


(* InspectionObject severity groupings. *)
$severity3 = {"Formatting", "Remark", "ImplicitTimes", "Scoping"};
$severity2 = {"Warning"};
$severity1 = {"Error", "Fatal"};


(* Calculate the number of lints for each severity in a given cell or list of cells. *)
lintSeverityCounts[cell_CellObject] :=
	With[
		{severities = Replace[
			Through[varValue[cell, "Lints"]["Severity"]],
			{Alternatives @@ $severity3 -> 3, Alternatives @@ $severity2 -> 2, Alternatives @@ $severity1 -> 1},
			1]},
		{checked = Replace[severities, Except[_List] -> {}]},
		(* Return an Association of the severity counts. *)
		varSet[{cell, "SeverityCounts"}, Association[Rule @@@ Sort[Tally[checked]]]]]


lintIconDisplayOpts = {"exclamSize" -> 9, FontSize -> 10, FontWeight -> "SemiBold", BaselinePosition -> Scaled[.02]};


(* Display an exclam (color-keyed to the severity), and a number to show the lint count. *)
lintSeverityCountsIcon[severity_, count_, OptionsPattern[lintIconDisplayOpts]] :=
	With[
		{icon = Row[
			{
				Show[iconData["Exclam"][colorData[severity]], ImageSize -> OptionValue["exclamSize"]{1, 1}, BaselinePosition -> OptionValue[BaselinePosition]],
				Spacer[1],
				styleData["CellBracketButton"][count, FontSize -> OptionValue[FontSize], FontWeight -> OptionValue[FontWeight]]}]},

		Style[icon, FontSize -> 1]]


(* lintSeverityCountsIconRow displays a row of the lint severity exclams and counts. *)
lintSeverityCountsIconRow[
	cellOrNotebook_ /; MatchQ[cellOrNotebook, _CellObject | _NotebookObject],
	opts:OptionsPattern[lintIconDisplayOpts]
] :=
	With[
		{iconRow =
			(* Map the cell-count-icon function over the severity count association. *)
			If[Head[cellOrNotebook] === CellObject,
				KeyValueMap[lintSeverityCountsIcon[#1, #2, opts]&, lintSeverityCounts[cellOrNotebook]],

				(* If cellOrNotebook is a notebook, then merge the severity count associations for all cells.
					Note that this relies on the severity counts having already been calculated and stored in the lintedCells Association.
					Therefore, it is possible that the "Severity" keys haven't been populated yet. If so, display nothing. *)
				Quiet @ Check[
					KeyValueMap[
						lintSeverityCountsIcon[#1, #2, opts]&,
						Merge[
							Replace[varValue[cellOrNotebook, All, "SeverityCounts"], Except[<|___Rule|>] -> <||>, 1],
							Total]],
					{Spacer[0]}]]},
		Row[
			Replace[
				Riffle[iconRow, Spacer[3]],
				(* If there are zero lints, then display a tick. *)
				{} -> {Show[
					iconData["TickDisk"][colorData["ApplyButtonBack"], colorData["ApplyButtonText"]],
					ImageSize -> OptionValue["exclamSize"]{1, 1}, BaselinePosition -> OptionValue[BaselinePosition]]}]]]


cellBracketButton[cell_CellObject] :=
	Cell[BoxData @ ToBoxes @
		cellManagement @ Tooltip[
			noRecursion @ button[
				Pane[lintSeverityCountsIconRow[cell], ContentPadding -> False, BaselinePosition -> (Scaled[-.15] -> Baseline)],

				(* Scroll the notebook to the bottom of the lint pod when clicked. *)
				SelectionMove[cell, After, Cell],

				"BackColor" -> colorData["CellBracketButtonBack"],
				"BackHoverColor" -> colorData["CellBracketButtonHover"],
				"EdgeColor" -> colorData["CellBracketButtonEdge"],
				"EdgeHoverColor" -> colorData["CellBracketButtonEdge"],
				ImageSize -> {Automatic, 14},
				FrameMargins -> {3{1, 1}, {1, 1}},
				Alignment -> {Center, Baseline}],

			"Go to code analysis"]]


(* noLintsBracketMarker gets attached to cells that were analyzed but didn't produce lints. *)
noLintsBracketMarker[cell_CellObject] :=
	With[{originalHash = FrontEndExecute[FrontEnd`CryptoHash[cell]][[2, -1]]},

		Cell[BoxData @ ToBoxes @ cellManagement @
			DynamicWrapper[
				Tooltip[
					noRecursion @ button[
						(* Just show the green tick. *)
						Show[
							iconData["TickDisk"][colorData["ApplyButtonBack"], colorData["ApplyButtonText"]],
							ImageSize -> 9{1, 1}, BaselinePosition -> Scaled[.02]],

						(* Delete the marker on click. *)
						With[{evalCell = EvaluationCell[]},
							SessionSubmit[ScheduledTask[NotebookDelete[evalCell], .1]]],

						"BackColor" -> colorData["CellBracketButtonBack"],
						"BackHoverColor" -> colorData["CellBracketButtonHover"],
						"EdgeColor" -> colorData["CellBracketButtonEdge"],
						"EdgeHoverColor" -> colorData["CellBracketButtonEdge"],
						ImageSize -> {Automatic, 14},
						FrameMargins -> {3{1, 1}, {1, 1}},
						Alignment -> {Center, Baseline}],
					
					"Code Analysis found no issues"],
				
				(* If the cell is edited, then remove the bracket marker. *)
				If[FrontEndExecute[FrontEnd`CryptoHash[cell]][[2, -1]] =!= originalHash,
					With[{evalCell = EvaluationCell[]},
						SessionSubmit[ScheduledTask[NotebookDelete[evalCell], .1]]]]]]]


(* ::Section::Closed:: *)
(*UI Generation Actions*)


(* ::Text:: *)
(*Most lint sources are going to be a part spec that points to a single token. However, for errors that involve implicit tokens, such as implicit times and implicit Null, the InspectionObject provides the source as After[{XXXX}] or Before[{XXXX}], where XXXX points to the entire expression before or after the implicit token. Seeing as we always just want to underlight one token, and we can't underlight the implicit token itself, we need to find the token that's directly adjacent to the implicit token. This is done using findToken, which achieves this by recursively taking the first or last part of the code boxes pointed to by the XXXX in After[{XXXX}] or Before[{XXXX}].*)


findToken[codeBoxes_, sourceRaw_] :=
	With[
		{source = Replace[sourceRaw, {} -> {0}]},
		{
			maxRecursions = 1000,
			(* recursivePart:
				* 0 means we already have an appropriate source,
				* -1 means we want to recursively find the last of the last of the last etc part, seeing as the implicit token comes after the provided source,
				* 1 means we want to recursively find the first of the first of the first etc part, seeing as the implicit token comes before the provided source. *)
			partToRecursivelyTake = Switch[source, _List, None, _After, -1, _Before, 1]
		},
		
		If[partToRecursivelyTake === None,
			(* source is already a single token. *)
			source,
			
			(* source is adjacent to an implicit token. *)
			NestWhile[
				Function[newSource, Append[newSource, partToRecursivelyTake]],
				First[source],
				(* When the expression becomes atomic, we've reached the last token. *)
				Function[newSource, !AtomQ[codeBoxes[[Sequence @@ newSource]]]],
				1, maxRecursions]]]


CodeInspector`LinterUI`$confidenceThreshold = .75;


(* Currently, there's no UX to deal with lints with coincident sources. refineSources removes such conflicts by picking the lint of highest confidence, and discarding the others. *)
refineSources[lints_?(MatchQ[{___CodeInspector`InspectionObject}]), cell_CellObject, codeBoxes_] :=
	With[
		(* Exclude lints with a confidence less than CodeInspector`LinterUI`$confidenceThreshold. *)
		{confidenceThreshold = CodeInspector`LinterUI`$confidenceThreshold},
		(* First, generate a list of all sources from all lints in the cell, so that we can test the sources for coincidence and then remove the corresponding lints if need be.
			(Note that each list element is actually <|"Source"->source, "Confidence"->confidence, "Lint"->lint|> so we know to which lint a source belongs and we can compare confidence levels).
			Note that lints can have additional sources, so just becuase *one* source coincides with another source, doesn't mean we discard that lint straight away.
			Instead, remove that source, and then Remove the lint vars if no sources remain. *)
		{sources =
			Flatten @ Map[
				Function[lint,
					(* Generate a list of all sources and additional sources for the lint. Turn each of those sources into an association so that its confidence level and corresponding lint can be queried. *)
					<|"Source" -> findToken[codeBoxes, #], "Confidence" -> lint[[4]][ConfidenceLevel], "Lint" -> lint|>& /@
						Prepend[
							Lookup[Last[lint], "AdditionalSources", {}],
							Last[lint][CodeParser`Source]]],
				lints]},
		
		(* Only keep sources with sufficiently high confidence. *)
		{clippedSources1 = Select[sources, #["Confidence"] >= confidenceThreshold &]},

		(* Only keep lints which haven't been disabled. *)
		{clippedSources2 = 
			Select[clippedSources1,
				AbsoluteCurrentValue[cell, makeTagOptionList[#["Lint"]]] =!= False &]},
		
		(* Group the sources, then reverse-sort the groups by confidence and take the last (highest confidence) in each. *)
		{filteredSources =
			First /@ ReverseSortBy[#["Confidence"] &] /@ GatherBy[clippedSources2, #["Source"] &]},
		
		(* Tidy up by grouping the sources by lint, and then extracting the sources and reverse-sorting them (so that the deepest
			tokens are marked up first, thus avoiding invalidating the part specs for other lints).
			Refactor the resulting association from:
			<|lint1 -> {source11, source12, ...}, lint2 -> {source21, source22, ...}, ...|>
			to:
			{<|"Lint" -> lint1, "Sources" -> {source11, source12, ...}|>, <|"Lint" -> lint2, "Sources" -> {source21, source22, ...}|>, ...}
			(this is a friendlier data-structure for the Fold that this gets fed into in getCellInfo). *)
		KeyValueMap[
			<|"Lint" -> #1, "Sources" -> #2|> &,
			GroupBy[filteredSources, #["Lint"]&, ReverseSort[Through[#["Source"]]]&]]]


markupCode[cell_CellObject, lint_CodeInspector`InspectionObject, sources_, codeBoxes_] :=
	Block[{raftAttachedQ, mouseOver},

		(* We want to mark up the cell boxes with underlights, highlighting, and lint rafts that appear on mouseover. *)
		(* The approach here is to recursively apply markups to the cell boxes, where Fold is used to supply the next lint after each markup is applied. So, Fold starts with the raw cell boxes... *)
		Fold[
			
			(* ...and uses MapAt to apply a wrapper around the boxes at a lint source. This wrapper uses a DynamicWrapperBox to do things like pop the lint raft on mouseover.
				The resulting boxes - marked up with one lint - then get marked up again by applying a wrapper at the next source.
				And thus the Fold proceeds like this over all the sources, finally returning the code boxes with a wrapper around each source.
				Note that this only works (at least in a stable, desirable manner) if the lint sources point to single tokens and therefore don't nest/overlap (of course two tokens could be coincident, but refineSources deals with this).
				All sources given by CodeInspectBox (should) point to a single token, so you never get nests/overlaps. *)
			Function[{foldedCodeBoxes, source},
				(* Note that if the source is {0}, we want to apply the wrapper to the entire token. MapAt instead applies the wrapper to the token head.
					Therefore, if the source is {0}, replace MapAt with #1[#2]&, which takes MapAt's first argument (the wrapper) and applies it to MapAt's second argument (the code boxes, which in this case is the entire token). *)
				If[source === {0}, #1[#2]&, MapAt][
					Function[token,
						(* Set the initial state of the lint as "inactive", meaning that the linted boxes are neither being hovered
							over ("hoverInPlace" or "hoverMooring"), nor is their raft open ("active"). Note that rafts appear both in-place in the marked-up
							code, and in the raft mooring at the bottom, so it is necessary for them to share state variables. *)
						varSet[{cell, lint, "State"}, "inactive"];
						
						With[
							{markup =
								DynamicModuleBox[{raftAttachedQ = False, mouseOver = False},
									(* Wrap the linted boxes in a DynamicWrapper that will manage hover effects and attachment of raft cells. *)
									Evaluate @ DynamicWrapperBox[
										(* Underlight the linted boxes, and tie their background to the state variable for that lint. *)
										StyleBox[token,
											FontVariations -> {"Underlight" -> colorData[lint["Severity"]]},
											(* Highlight the linted boxes if the lint state is "hoverXXXX" or "active". *)
											Background -> Dynamic[Switch[varValue[cell, lint, "State"],
												(* The severity 1 color is a bit dark for highlighting compared to the others, so replace it with Pink. *)
												"inactive", Opacity[.2, Replace[colorData[lint["Severity"]], RGBColor[0.827451, 0.00392157, 0.00392157] -> Pink]],
												"hoverInPlace" | "hoverMooring" | "active", colorData["CodeHighlight"],
												(* Failsafe. *)
												_, None]]],
										
										mouseOver = CurrentValue["MouseOver"];
										(* Update the lint state variable so that other instances of this lint (other sources, and rafts in
											the mooring) know whether this lint is being hovered over. "active" takes presedence over "hoverXXXX". *)
										If[mouseOver && !MatchQ[varValue[cell, lint, "State"], "active" | "hoverMooring"], varSet[{cell, lint, "State"}, "hoverInPlace"]];
										(* Attach the lint raft on mouseover of the linted boxes (given the absence of an existing raft). *)
										If[mouseOver && !raftAttachedQ,
											raftAttachedQ = True;
											AttachCell[EvaluationBox[],
												varValue[cell, lint, "Raft"][
													"inPlace",
													Deinitialization :> (raftAttachedQ = False; varSet[{cell, lint, "State"}, "inactive"])],
												
												(* Check the mouse position and anchor the raft such that it isn't clipped by a window edge. *)
												Sequence @@ With[{mousePos = First[Replace[MousePosition["WindowScaled"], None -> {.5, 0}]]},
													(* If the spawn point is: *)
													Which[
														(* In the left-third of the window, then left-align the lint token and raft. *)
														Between[mousePos, {0, .33}],
														{{Left, Bottom}, {0, 0}, {Left, Top}},
														(* In the middle-third of the window, then center-align the lint token and raft. *)
														Between[mousePos, {.33, .67}],
														{{Center, Bottom}, {0, 0}, {Center, Top}},
														(* In the right-third of the window, then right-align the lint token and raft. *)
														Between[mousePos, {.67, 1}],
														{{Right, Bottom}, {0, 0}, {Right, Top}},
														(* Failsafe. *)
														True,
														{{Center, Bottom}, {0, 0}, {Center, Top}}]],
												
												RemovalConditions -> {"MouseExit"}]],
												
										(* This only needs to update with CurrentValue["MouseOver"]. *)
										TrackedSymbols :> {}],
									
									DynamicModuleValues :> {}]},
							
							
							(* Seeing as we have wrapped the token in a DynamicModuleBox, the token is no longer going to be parsed properly, and can therefore result in incorrect syntax coloring, incorrect spacing etc.
								By wrapping the DynamicModuleBox in a TagBox with SyntaxForm -> token, the TagBox will then be parsed as if it were the original token. However, we need to check that only one token is
								being wrapped, and so we only apply the TagBox if token is a string. *)
							(* BUG: Note that in 12.3, syntax coloring does not respect the SyntaxForm option. jfultz is fixing this for 12.3.1 *)
							(* BUG: Delimiter sizes also don't respect the SyntaxForm option (for example, if you had ``{FractionBox[1,2]}``, and the ``}`` was marked up, then the ``{`` would draw tall, but the ``}`` would draw normal height).
								jfultz tells me there is no straight-forward way to fix this, so we are just accepting this bug. *)
							If[Head[token] === String,
								TagBox[markup, "tag", SyntaxForm -> token],
								markup]]],
								
					foldedCodeBoxes, source]],

			(* Wrap the styling function around the boxes of the cell... *)
			codeBoxes,
			(* ...at each of the lint sources. *)
			sources]]


getCellInfo[cell_] :=
	Module[
		(* contents is the complete cell expression. codeBoxes is Cell[BoxData[codeBoxes], ...] and is the boxes passed to CodeInspectBox. *)
		{contents = NotebookRead[cell], codeBoxes, unfilteredLints, allLintsAndTheirSources, lints},

		(* Check that the cell is of the expected form, and that its BoxData produces lints: *)
		If[
			Or[
				(* First check that NotebookRead returned a Cell of the form we're expecting. *)
				!MatchQ[contents, Cell[BoxData[_], ___]],
				(* Then analyze the cell and check that it produced lints. *)
				(unfilteredLints = CodeInspector`CodeInspectBox[codeBoxes = First[First[contents]]]) === {},
				(* Then check that some lints remain after filtering. *)
				(allLintsAndTheirSources = refineSources[unfilteredLints, cell, codeBoxes]) === {}],

			(* If the cell produced no lints, return Nothing. *)
			Nothing,

			(* ----- If the cell produced lints, populate the various lint variables and return the cell: ----- *)

			lints = Through[allLintsAndTheirSources["Lint"]];

			(* The cell object. *)
			varSet[{cell, "Cell"}, cell];
			(* The entire cell expression, upon which code actions will be applied. The value of this variable will change when
				an edit is made via a raft menu item (i.e. "Replace this thing", "Delete this thing", etc.). *)
			varSet[{cell, "CellContents"}, contents];
			(* "MarkedUpCode" is the box contents of the cell, marked up (underlights, highlighting, etc.) according to its lints. *)
			varSet[{cell, "MarkedUpCode"},
				Fold[
					Function[{markedUpCodeBoxes, oneLintAndItsSources},
						markupCode[cell, oneLintAndItsSources["Lint"], oneLintAndItsSources["Sources"], markedUpCodeBoxes]],
					(* Start with the BoxData contents of the cell... *)
					codeBoxes,
					(* ...and mark it up according to a lint, carrying the markup forwards to the next markup application. *)
					allLintsAndTheirSources]];
			(* "LintRafts" are raft cells that will appear in place under marked-up boxes, and in the mooring.
				There are two species, raftCell["inPlace"] and raftCell["mooring"]. *)
			varSet[{cell, "LintRafts"}, makeRaftCell[cell, #]& /@ lints];
			(* "Lints" are the raw InspectionObjects. These are used to calculate the counts of each severity for the cell bracket button. *)
			varSet[{cell, "Lints"}, lints];
			(* "SeverityCounts" is an Association in which the keys are severity levels (1, 2, 3), and the values are lint counts.
				This is used for listing the lint counts in the cell marker buttons, and the Code Analysis docked cell. *)
			varSet[{cell, "SeverityCounts"}, <||>];
			(* "Hash" is the cell's "ShiftEnterHash" and will be used to check if a cell has been modified after
				analysis, and thus if the analysis needs to be refreshed. *)
			varSet[{cell, "Hash"}, FrontEndExecute[FrontEnd`CryptoHash[cell]][[2, -1]]];
			(* "EditsMadeQ" switches to True if a CodeAction has been applied to the code copy. *)
			varSet[{cell, "EditsMadeQ"}, False];
			(* The linting pods for Code cells and Input cells will be rendered
				differently (size, fill, etc.), so we need to know what the cell type is. *)
			varSet[{cell, "Type"}, CurrentValue[cell, CellStyle]];
			(* "Width" and "H(orizontal)Margins" are used to set the size of the linting pod. *)
			varSet[{cell, "Width"}, AbsoluteCurrentValue[cell, CellSize]];
			varSet[{cell, "HMargins"}, First[AbsoluteCurrentValue[cell, CellMargins]]];
			(* Return the cell. *)
			cell]]


(* analyzeAction filters the given cells (the user can provide an entire notebook, or a list of cells) so that only
	"Input" and "Code" cells remain, and then applies getCellInfo to them, which analyzes them and populates the various
	lint variables. *)
analyzeAction[
	HoldPattern[notebookOrCells_:EvaluationNotebook[]]
] /; MatchQ[notebookOrCells, _NotebookObject | {__CellObject}] :=
	With[
		(* If the arg is a list of cells, then just assign it to cellsToLint.
			Otherwise, the arg is a notebook object, so retrieve the cells in that notebook and assign to cellsToLint. *)
		{cellsToLint = If[ListQ[notebookOrCells], notebookOrCells, Cells[notebookOrCells]]},
		
		(* We only want to analyze "Input" and "Code" cells, so generate a list of all those cells in the notebook / given list of cells. *)
		{cellsToLintRefinement1 = Select[cellsToLint, MatchQ[CurrentValue[#, CellStyle], {"Code"} | {"Input"}]&]},
		
		(* Map getCellInfo over the cells to populate the lint variables. The output of this map is a
			list of the cells that produced lints, so reassign cellsToLint to this. *)
		{cellsToLintRefinement2 = getCellInfo /@ cellsToLintRefinement1},
			
		(* There may be lint pods already present in the notebook. Return only the newly-linted cells. *)
		(* Also return the cells that qualified for analysis (i.e. are input/code cells) but produced no lints. *)
		{cellsToLintRefinement2, Complement[cellsToLintRefinement1, cellsToLintRefinement2]}]


attachAnalysisAction[
	HoldPattern[notebookOrCells_:EvaluationNotebook[]]
] /; MatchQ[notebookOrCells, _NotebookObject | {__CellObject}] :=
	Module[
		{cells, cellAssoc, notebookObj, cleanCells, cleanCellsAssoc},

		(* These should already be loaded, but just make sure. *)
		Needs["CodeParser`"];
		Needs["CodeInspector`"];

		If[ListQ[notebookOrCells],

			(* If the arg is a list of cells, then assign it to cells. *)
			cells = notebookOrCells;
			(* Get the parent notebook. attachAnalysisAction assumes that all cells are from the same notebook. *)
			notebookObj = ParentNotebook[First[cells]];
			(* Delete any existing attached lint pod cells. *)
			NotebookDelete /@ Flatten[
				Function[cell, varValue[cell, "UIAttachedCells"]] /@ cells],

			(* If the arg is a notebook, then assign the existing linted cells to ``cells``, returning {} if the notebook has not yet been analyzed. *)
			notebookObj = notebookOrCells;
			cells = Quiet @ Replace[varValue[notebookObj, All, "CellObject"], Except[_List] -> {}];
			(* Delete any existing attached lint pod cells. *)
			NotebookDelete /@ Flatten[varValue[notebookOrCells, All, "UIAttachedCells"]]];


		varSet[{notebookObj, "AnalysisInProgressQ"}, True];

		(* If analyzing a notebook object, and the docked cell isn't attached, attach the docked cell. *)
		If[
			Head[notebookOrCells] === NotebookObject && !TrueQ[varValue[notebookOrCells, "DockedCellPresentQ"]],
			varSet[{notebookObj, "DockedCellPresentQ"}, True];
			CurrentValue[notebookOrCells, DockedCells] = 
				Append[
					Flatten[{CurrentValue[notebookOrCells, DockedCells]}],
					FEPrivate`FrontEndResource["CodeInspectorExpressions", "DockedCell"]]];
		
		(* Analyze the notebook / cells. *)
		(* cells are the cells that produced lints, and cleanCells are the input/code cells that didn't produce lints. We'll attach bracket markers to the clean ones. *)
		{cells, cleanCells} = analyzeAction[notebookOrCells];

		(* Create the boxes for the lint-pods and cell-bracket-buttons. *)
		cellAssoc = 
			Association @ Map[
				Function[cell,
					With[
						{
							hMargins = varValue[cell, "HMargins"],
							hMarginsFudgeFactor = {-13, (*-13*)4},
							vMargins = {5, 5}},

						cell -> <|
							
							"LintPodBoxes" -> Cell[BoxData @ ToBoxes @
								cellManagement @ lintPod[cell, varValue[cell, "Type"]],
								LineBreakWithin -> Automatic,
								CellMargins -> {hMargins + hMarginsFudgeFactor, vMargins}],

							"CellBracketButtonBoxes" -> cellBracketButton[cell]|>]],
				cells];
			
		(* Create boxes for the clean cells bracket markers. *)
		cleanCellsAssoc = Association[Function[cell, cell -> noLintsBracketMarker[cell]] /@ cleanCells];

		(* We made the cell expressions for the UI cells before attaching them so that the attachemnt process can happen quickly. *)

		(* Delete any existing bracket marker. *)
		Function[cell, NotebookDelete[varValue[cell, "CleanCellBracketMarker"]]] /@ cleanCells;

		(* Attach the clean cell bracket markers. *)
		KeyValueMap[
			Function[{cell, uiCellBoxes},
				(* Attach the new bracket marker and store its CellObject. *)
				With[
					{markerCell = AttachCell[cell, uiCellBoxes, {"CellBracket", Top}, {0, 0}, {Right, Top}]},
					varSet[{cell, "CleanCellBracketMarker"}, markerCell]]],
			cleanCellsAssoc];
		
		(* Attach the lint pods and cell bracket buttons. *)
		KeyValueMap[
			Function[{cell, uiCellBoxes},
				With[
					{
						(* The lint pod cell. *)
						podCell = AttachCell[cell, uiCellBoxes["LintPodBoxes"], "Inline"],
						(* Also attach a marker on the input/code cell bracket that takes you to the lint pod when clicked. *)
						bracketCell = AttachCell[cell, uiCellBoxes["CellBracketButtonBoxes"], {"CellBracket", Top}, {0, 0}, {Right, Top}]},
				
					varSet[{cell, "UIAttachedCells"}, {bracketCell, podCell}]]],

			cellAssoc];
		
		Clear[cellAssoc];
		
		varSet[{notebookObj, "AnalysisInProgressQ"}, False];
		
		(* Tickle the docked cell. *)
		CodeInspector`LinterUI`Private`DynamicTriggers`dockedCellLintCounts = RandomReal[];]


(* ::Section::Closed:: *)
(*Package Footer*)


End[]
EndPackage[]
